summaryrefslogtreecommitdiffstats
path: root/src/bin/dhcp4
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/dhcp4')
-rw-r--r--src/bin/dhcp4/Makefile.am148
-rw-r--r--src/bin/dhcp4/Makefile.in1056
-rw-r--r--src/bin/dhcp4/client_handler.cc334
-rw-r--r--src/bin/dhcp4/client_handler.h248
-rw-r--r--src/bin/dhcp4/ctrl_dhcp4_srv.cc1394
-rw-r--r--src/bin/dhcp4/ctrl_dhcp4_srv.h473
-rw-r--r--src/bin/dhcp4/dhcp4.dox352
-rw-r--r--src/bin/dhcp4/dhcp4_hooks.dox484
-rw-r--r--src/bin/dhcp4/dhcp4_lexer.cc6518
-rw-r--r--src/bin/dhcp4/dhcp4_lexer.ll2375
-rw-r--r--src/bin/dhcp4/dhcp4_log.cc43
-rw-r--r--src/bin/dhcp4/dhcp4_log.h127
-rw-r--r--src/bin/dhcp4/dhcp4_messages.cc333
-rw-r--r--src/bin/dhcp4/dhcp4_messages.h170
-rw-r--r--src/bin/dhcp4/dhcp4_messages.mes953
-rw-r--r--src/bin/dhcp4/dhcp4_parser.cc6151
-rw-r--r--src/bin/dhcp4/dhcp4_parser.h5425
-rw-r--r--src/bin/dhcp4/dhcp4_parser.yy2866
-rw-r--r--src/bin/dhcp4/dhcp4_srv.cc4351
-rw-r--r--src/bin/dhcp4/dhcp4_srv.h1200
-rw-r--r--src/bin/dhcp4/dhcp4o6.dox62
-rw-r--r--src/bin/dhcp4/dhcp4to6_ipc.cc191
-rw-r--r--src/bin/dhcp4/dhcp4to6_ipc.h56
-rw-r--r--src/bin/dhcp4/json_config_parser.cc836
-rw-r--r--src/bin/dhcp4/json_config_parser.h66
-rw-r--r--src/bin/dhcp4/location.hh306
-rw-r--r--src/bin/dhcp4/main.cc302
-rw-r--r--src/bin/dhcp4/parser_context.cc244
-rw-r--r--src/bin/dhcp4/parser_context.h417
-rw-r--r--src/bin/dhcp4/parser_context_decl.h20
-rw-r--r--src/bin/dhcp4/tests/Makefile.am186
-rw-r--r--src/bin/dhcp4/tests/Makefile.in1742
-rw-r--r--src/bin/dhcp4/tests/callout_library_1.cc30
-rw-r--r--src/bin/dhcp4/tests/callout_library_2.cc16
-rw-r--r--src/bin/dhcp4/tests/callout_library_3.cc78
-rw-r--r--src/bin/dhcp4/tests/callout_library_common.h96
-rw-r--r--src/bin/dhcp4/tests/classify_unittest.cc1443
-rw-r--r--src/bin/dhcp4/tests/client_handler_unittest.cc893
-rw-r--r--src/bin/dhcp4/tests/config_backend_unittest.cc506
-rw-r--r--src/bin/dhcp4/tests/config_parser_unittest.cc7577
-rw-r--r--src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc2092
-rw-r--r--src/bin/dhcp4/tests/d2_unittest.cc525
-rw-r--r--src/bin/dhcp4/tests/d2_unittest.h115
-rw-r--r--src/bin/dhcp4/tests/decline_unittest.cc292
-rw-r--r--src/bin/dhcp4/tests/dhcp4_client.cc600
-rw-r--r--src/bin/dhcp4/tests/dhcp4_client.h533
-rw-r--r--src/bin/dhcp4/tests/dhcp4_process_tests.sh.in477
-rw-r--r--src/bin/dhcp4/tests/dhcp4_srv_unittest.cc4855
-rw-r--r--src/bin/dhcp4/tests/dhcp4_test_utils.cc980
-rw-r--r--src/bin/dhcp4/tests/dhcp4_test_utils.h786
-rw-r--r--src/bin/dhcp4/tests/dhcp4_unittests.cc26
-rw-r--r--src/bin/dhcp4/tests/dhcp4to6_ipc_unittest.cc411
-rw-r--r--src/bin/dhcp4/tests/direct_client_unittest.cc435
-rw-r--r--src/bin/dhcp4/tests/dora_unittest.cc2792
-rw-r--r--src/bin/dhcp4/tests/fqdn_unittest.cc2644
-rw-r--r--src/bin/dhcp4/tests/get_config_unittest.cc12340
-rw-r--r--src/bin/dhcp4/tests/get_config_unittest.cc.skel374
-rw-r--r--src/bin/dhcp4/tests/get_config_unittest.h27
-rw-r--r--src/bin/dhcp4/tests/hooks_unittest.cc3120
-rw-r--r--src/bin/dhcp4/tests/host_options_unittest.cc554
-rw-r--r--src/bin/dhcp4/tests/host_unittest.cc828
-rw-r--r--src/bin/dhcp4/tests/inform_unittest.cc653
-rw-r--r--src/bin/dhcp4/tests/kea_controller_unittest.cc1094
-rw-r--r--src/bin/dhcp4/tests/marker_file.cc60
-rw-r--r--src/bin/dhcp4/tests/marker_file.h.in62
-rw-r--r--src/bin/dhcp4/tests/out_of_range_unittest.cc534
-rw-r--r--src/bin/dhcp4/tests/parser_unittest.cc973
-rw-r--r--src/bin/dhcp4/tests/release_unittest.cc299
-rw-r--r--src/bin/dhcp4/tests/shared_network_unittest.cc2876
-rw-r--r--src/bin/dhcp4/tests/simple_parser4_unittest.cc215
-rw-r--r--src/bin/dhcp4/tests/test_data_files_config.h.in9
-rw-r--r--src/bin/dhcp4/tests/test_libraries.h.in32
-rw-r--r--src/bin/dhcp4/tests/vendor_opts_unittest.cc1641
73 files changed, 93322 insertions, 0 deletions
diff --git a/src/bin/dhcp4/Makefile.am b/src/bin/dhcp4/Makefile.am
new file mode 100644
index 0000000..f53e75c
--- /dev/null
+++ b/src/bin/dhcp4/Makefile.am
@@ -0,0 +1,148 @@
+SUBDIRS = . tests
+
+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 += $(BOOST_INCLUDES)
+if HAVE_MYSQL
+AM_CPPFLAGS += $(MYSQL_CPPFLAGS)
+endif
+if HAVE_PGSQL
+AM_CPPFLAGS += $(PGSQL_CPPFLAGS)
+endif
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+EXTRA_DIST = dhcp4.dox dhcp4_hooks.dox dhcp4o6.dox
+EXTRA_DIST += dhcp4_parser.yy
+
+
+# convenience archive
+
+noinst_LTLIBRARIES = libdhcp4.la
+
+libdhcp4_la_SOURCES =
+libdhcp4_la_SOURCES += ctrl_dhcp4_srv.cc ctrl_dhcp4_srv.h
+libdhcp4_la_SOURCES += json_config_parser.cc json_config_parser.h
+libdhcp4_la_SOURCES += dhcp4_log.cc dhcp4_log.h
+libdhcp4_la_SOURCES += dhcp4_srv.cc dhcp4_srv.h
+libdhcp4_la_SOURCES += dhcp4to6_ipc.cc dhcp4to6_ipc.h
+libdhcp4_la_SOURCES += client_handler.cc client_handler.h
+libdhcp4_la_SOURCES += dhcp4_lexer.ll location.hh
+libdhcp4_la_SOURCES += dhcp4_parser.cc dhcp4_parser.h
+libdhcp4_la_SOURCES += parser_context.cc parser_context.h parser_context_decl.h
+libdhcp4_la_SOURCES += dhcp4_messages.h dhcp4_messages.cc
+EXTRA_DIST += dhcp4_messages.mes
+
+sbin_PROGRAMS = kea-dhcp4
+
+kea_dhcp4_SOURCES = main.cc
+
+kea_dhcp4_LDADD = libdhcp4.la
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/http/libkea-http.la
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+
+if HAVE_MYSQL
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/mysql/libkea-mysql.la
+endif
+if HAVE_PGSQL
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/pgsql/libkea-pgsql.la
+endif
+
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/database/libkea-database.la
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/process/libkea-process.la
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+kea_dhcp4_LDADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS)
+
+kea_dhcp4_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+if HAVE_MYSQL
+kea_dhcp4_LDFLAGS += $(MYSQL_LIBS)
+endif
+if HAVE_PGSQL
+kea_dhcp4_LDFLAGS += $(PGSQL_LIBS)
+endif
+
+kea_dhcp4dir = $(pkgdatadir)
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f dhcp4_messages.h dhcp4_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file
+messages: dhcp4_messages.h dhcp4_messages.cc
+ @echo Message files regenerated
+
+dhcp4_messages.h dhcp4_messages.cc: dhcp4_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/bin/dhcp4/dhcp4_messages.mes
+
+else
+
+messages dhcp4_messages.h dhcp4_messages.cc:
+ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
+
+if GENERATE_PARSER
+
+# Generate parser first.
+all-recursive: dhcp4_lexer.cc location.hh dhcp4_parser.cc dhcp4_parser.h
+
+parser: dhcp4_lexer.cc location.hh dhcp4_parser.cc dhcp4_parser.h
+ @echo "Flex/bison files regenerated"
+
+# --- Flex/Bison stuff below --------------------------------------------------
+# When debugging grammar issues, it's useful to add -v to bison parameters.
+# bison will generate parser.output file that explains the whole grammar.
+# It can be used to manually follow what's going on in the parser.
+# This is especially useful if yydebug_ is set to 1 as that variable
+# will cause parser to print out its internal state.
+# Call flex with -s to check that the default rule can be suppressed
+# Call bison with -W to get warnings like unmarked empty rules
+# Note C++11 deprecated register still used by flex < 2.6.0
+location.hh dhcp4_parser.cc dhcp4_parser.h: dhcp4_parser.yy
+ $(YACC) -Wno-yacc --defines=dhcp4_parser.h --report=all \
+ --report-file=dhcp4_parser.report -o dhcp4_parser.cc dhcp4_parser.yy
+
+dhcp4_lexer.cc: dhcp4_lexer.ll
+ $(LEX) --prefix parser4_ -o dhcp4_lexer.cc dhcp4_lexer.ll
+
+else
+
+parser location.hh dhcp4_parser.cc dhcp4_parser.h dhcp4_lexer.cc:
+ @echo Parser generation disabled. Configure with --enable-generate-parser to enable it.
+
+endif
diff --git a/src/bin/dhcp4/Makefile.in b/src/bin/dhcp4/Makefile.in
new file mode 100644
index 0000000..9a812a9
--- /dev/null
+++ b/src/bin/dhcp4/Makefile.in
@@ -0,0 +1,1056 @@
+# 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_MYSQL_TRUE@am__append_1 = $(MYSQL_CPPFLAGS)
+@HAVE_PGSQL_TRUE@am__append_2 = $(PGSQL_CPPFLAGS)
+sbin_PROGRAMS = kea-dhcp4$(EXEEXT)
+@HAVE_MYSQL_TRUE@am__append_3 = $(top_builddir)/src/lib/mysql/libkea-mysql.la
+@HAVE_PGSQL_TRUE@am__append_4 = $(top_builddir)/src/lib/pgsql/libkea-pgsql.la
+@HAVE_MYSQL_TRUE@am__append_5 = $(MYSQL_LIBS)
+@HAVE_PGSQL_TRUE@am__append_6 = $(PGSQL_LIBS)
+subdir = src/bin/dhcp4
+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_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_sysrepo.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 =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(sbindir)"
+PROGRAMS = $(sbin_PROGRAMS)
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libdhcp4_la_LIBADD =
+am_libdhcp4_la_OBJECTS = ctrl_dhcp4_srv.lo json_config_parser.lo \
+ dhcp4_log.lo dhcp4_srv.lo dhcp4to6_ipc.lo client_handler.lo \
+ dhcp4_lexer.lo dhcp4_parser.lo parser_context.lo \
+ dhcp4_messages.lo
+libdhcp4_la_OBJECTS = $(am_libdhcp4_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 =
+am_kea_dhcp4_OBJECTS = main.$(OBJEXT)
+kea_dhcp4_OBJECTS = $(am_kea_dhcp4_OBJECTS)
+am__DEPENDENCIES_1 =
+kea_dhcp4_DEPENDENCIES = libdhcp4.la \
+ $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la \
+ $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la \
+ $(top_builddir)/src/lib/eval/libkea-eval.la \
+ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+ $(top_builddir)/src/lib/stats/libkea-stats.la \
+ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+ $(top_builddir)/src/lib/http/libkea-http.la \
+ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la $(am__append_3) \
+ $(am__append_4) \
+ $(top_builddir)/src/lib/database/libkea-database.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/process/libkea-process.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+kea_dhcp4_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(kea_dhcp4_LDFLAGS) $(LDFLAGS) -o $@
+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)/client_handler.Plo \
+ ./$(DEPDIR)/ctrl_dhcp4_srv.Plo ./$(DEPDIR)/dhcp4_lexer.Plo \
+ ./$(DEPDIR)/dhcp4_log.Plo ./$(DEPDIR)/dhcp4_messages.Plo \
+ ./$(DEPDIR)/dhcp4_parser.Plo ./$(DEPDIR)/dhcp4_srv.Plo \
+ ./$(DEPDIR)/dhcp4to6_ipc.Plo \
+ ./$(DEPDIR)/json_config_parser.Plo ./$(DEPDIR)/main.Po \
+ ./$(DEPDIR)/parser_context.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 =
+LEXCOMPILE = $(LEX) $(AM_LFLAGS) $(LFLAGS)
+LTLEXCOMPILE = $(LIBTOOL) $(AM_V_lt) $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(LEX) $(AM_LFLAGS) $(LFLAGS)
+AM_V_LEX = $(am__v_LEX_@AM_V@)
+am__v_LEX_ = $(am__v_LEX_@AM_DEFAULT_V@)
+am__v_LEX_0 = @echo " LEX " $@;
+am__v_LEX_1 =
+YLWRAP = $(top_srcdir)/ylwrap
+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 = $(libdhcp4_la_SOURCES) $(kea_dhcp4_SOURCES)
+DIST_SOURCES = $(libdhcp4_la_SOURCES) $(kea_dhcp4_SOURCES)
+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
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp \
+ $(top_srcdir)/ylwrap dhcp4_lexer.cc
+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_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_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_SYSREPO = @HAVE_SYSREPO@
+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@
+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_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+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 = . tests
+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 $(BOOST_INCLUDES) \
+ $(am__append_1) $(am__append_2)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda
+EXTRA_DIST = dhcp4.dox dhcp4_hooks.dox dhcp4o6.dox dhcp4_parser.yy \
+ dhcp4_messages.mes
+
+# convenience archive
+noinst_LTLIBRARIES = libdhcp4.la
+libdhcp4_la_SOURCES = ctrl_dhcp4_srv.cc ctrl_dhcp4_srv.h \
+ json_config_parser.cc json_config_parser.h dhcp4_log.cc \
+ dhcp4_log.h dhcp4_srv.cc dhcp4_srv.h dhcp4to6_ipc.cc \
+ dhcp4to6_ipc.h client_handler.cc client_handler.h \
+ dhcp4_lexer.ll location.hh dhcp4_parser.cc dhcp4_parser.h \
+ parser_context.cc parser_context.h parser_context_decl.h \
+ dhcp4_messages.h dhcp4_messages.cc
+kea_dhcp4_SOURCES = main.cc
+kea_dhcp4_LDADD = libdhcp4.la \
+ $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la \
+ $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la \
+ $(top_builddir)/src/lib/eval/libkea-eval.la \
+ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+ $(top_builddir)/src/lib/stats/libkea-stats.la \
+ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+ $(top_builddir)/src/lib/http/libkea-http.la \
+ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la $(am__append_3) \
+ $(am__append_4) \
+ $(top_builddir)/src/lib/database/libkea-database.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/process/libkea-process.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS)
+kea_dhcp4_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(am__append_5) \
+ $(am__append_6)
+kea_dhcp4dir = $(pkgdatadir)
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .cc .ll .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/dhcp4/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/bin/dhcp4/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):
+install-sbinPROGRAMS: $(sbin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(sbindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(sbindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(sbindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-sbinPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(sbindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(sbindir)" && rm -f $$files
+
+clean-sbinPROGRAMS:
+ @list='$(sbin_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}; \
+ }
+
+libdhcp4.la: $(libdhcp4_la_OBJECTS) $(libdhcp4_la_DEPENDENCIES) $(EXTRA_libdhcp4_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(CXXLINK) $(libdhcp4_la_OBJECTS) $(libdhcp4_la_LIBADD) $(LIBS)
+
+kea-dhcp4$(EXEEXT): $(kea_dhcp4_OBJECTS) $(kea_dhcp4_DEPENDENCIES) $(EXTRA_kea_dhcp4_DEPENDENCIES)
+ @rm -f kea-dhcp4$(EXEEXT)
+ $(AM_V_CXXLD)$(kea_dhcp4_LINK) $(kea_dhcp4_OBJECTS) $(kea_dhcp4_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/client_handler.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ctrl_dhcp4_srv.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_lexer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_srv.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4to6_ipc.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/json_config_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parser_context.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 $@ $<
+
+.ll.cc:
+ $(AM_V_LEX)$(am__skiplex) $(SHELL) $(YLWRAP) $< $(LEX_OUTPUT_ROOT).c $@ -- $(LEXCOMPILE)
+
+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
+
+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
+check: check-recursive
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(sbindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+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)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+ -rm -f dhcp4_lexer.cc
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ clean-sbinPROGRAMS mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/client_handler.Plo
+ -rm -f ./$(DEPDIR)/ctrl_dhcp4_srv.Plo
+ -rm -f ./$(DEPDIR)/dhcp4_lexer.Plo
+ -rm -f ./$(DEPDIR)/dhcp4_log.Plo
+ -rm -f ./$(DEPDIR)/dhcp4_messages.Plo
+ -rm -f ./$(DEPDIR)/dhcp4_parser.Plo
+ -rm -f ./$(DEPDIR)/dhcp4_srv.Plo
+ -rm -f ./$(DEPDIR)/dhcp4to6_ipc.Plo
+ -rm -f ./$(DEPDIR)/json_config_parser.Plo
+ -rm -f ./$(DEPDIR)/main.Po
+ -rm -f ./$(DEPDIR)/parser_context.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-sbinPROGRAMS
+
+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)/client_handler.Plo
+ -rm -f ./$(DEPDIR)/ctrl_dhcp4_srv.Plo
+ -rm -f ./$(DEPDIR)/dhcp4_lexer.Plo
+ -rm -f ./$(DEPDIR)/dhcp4_log.Plo
+ -rm -f ./$(DEPDIR)/dhcp4_messages.Plo
+ -rm -f ./$(DEPDIR)/dhcp4_parser.Plo
+ -rm -f ./$(DEPDIR)/dhcp4_srv.Plo
+ -rm -f ./$(DEPDIR)/dhcp4to6_ipc.Plo
+ -rm -f ./$(DEPDIR)/json_config_parser.Plo
+ -rm -f ./$(DEPDIR)/main.Po
+ -rm -f ./$(DEPDIR)/parser_context.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic \
+ maintainer-clean-local
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-sbinPROGRAMS
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES clean-sbinPROGRAMS 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-sbinPROGRAMS install-strip \
+ installcheck installcheck-am installdirs installdirs-am \
+ maintainer-clean maintainer-clean-generic \
+ maintainer-clean-local mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-sbinPROGRAMS
+
+.PRECIOUS: Makefile
+
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f dhcp4_messages.h dhcp4_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+# Define rule to build logging source files from message file
+@GENERATE_MESSAGES_TRUE@messages: dhcp4_messages.h dhcp4_messages.cc
+@GENERATE_MESSAGES_TRUE@ @echo Message files regenerated
+
+@GENERATE_MESSAGES_TRUE@dhcp4_messages.h dhcp4_messages.cc: dhcp4_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/bin/dhcp4/dhcp4_messages.mes
+
+@GENERATE_MESSAGES_FALSE@messages dhcp4_messages.h dhcp4_messages.cc:
+@GENERATE_MESSAGES_FALSE@ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+# Generate parser first.
+@GENERATE_PARSER_TRUE@all-recursive: dhcp4_lexer.cc location.hh dhcp4_parser.cc dhcp4_parser.h
+
+@GENERATE_PARSER_TRUE@parser: dhcp4_lexer.cc location.hh dhcp4_parser.cc dhcp4_parser.h
+@GENERATE_PARSER_TRUE@ @echo "Flex/bison files regenerated"
+
+# --- Flex/Bison stuff below --------------------------------------------------
+# When debugging grammar issues, it's useful to add -v to bison parameters.
+# bison will generate parser.output file that explains the whole grammar.
+# It can be used to manually follow what's going on in the parser.
+# This is especially useful if yydebug_ is set to 1 as that variable
+# will cause parser to print out its internal state.
+# Call flex with -s to check that the default rule can be suppressed
+# Call bison with -W to get warnings like unmarked empty rules
+# Note C++11 deprecated register still used by flex < 2.6.0
+@GENERATE_PARSER_TRUE@location.hh dhcp4_parser.cc dhcp4_parser.h: dhcp4_parser.yy
+@GENERATE_PARSER_TRUE@ $(YACC) -Wno-yacc --defines=dhcp4_parser.h --report=all \
+@GENERATE_PARSER_TRUE@ --report-file=dhcp4_parser.report -o dhcp4_parser.cc dhcp4_parser.yy
+
+@GENERATE_PARSER_TRUE@dhcp4_lexer.cc: dhcp4_lexer.ll
+@GENERATE_PARSER_TRUE@ $(LEX) --prefix parser4_ -o dhcp4_lexer.cc dhcp4_lexer.ll
+
+@GENERATE_PARSER_FALSE@parser location.hh dhcp4_parser.cc dhcp4_parser.h dhcp4_lexer.cc:
+@GENERATE_PARSER_FALSE@ @echo Parser generation disabled. Configure with --enable-generate-parser to enable it.
+
+# 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/dhcp4/client_handler.cc b/src/bin/dhcp4/client_handler.cc
new file mode 100644
index 0000000..58c071d
--- /dev/null
+++ b/src/bin/dhcp4/client_handler.cc
@@ -0,0 +1,334 @@
+// 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 <dhcp4/client_handler.h>
+#include <dhcp4/dhcp4_log.h>
+#include <exceptions/exceptions.h>
+#include <stats/stats_mgr.h>
+#include <util/multi_threading_mgr.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::log;
+
+namespace isc {
+namespace dhcp {
+
+ClientHandler::Client::Client(Pkt4Ptr query, DuidPtr client_id,
+ HWAddrPtr hwaddr)
+ : query_(query), htype_(HTYPE_ETHER), thread_(this_thread::get_id()) {
+ // Sanity checks.
+ if (!query) {
+ isc_throw(InvalidParameter, "null query in ClientHandler");
+ }
+ if (!client_id && (!hwaddr || hwaddr->hwaddr_.empty())) {
+ isc_throw(InvalidParameter,
+ "null client-id and hwaddr in ClientHandler");
+ }
+
+ if (client_id) {
+ duid_ = client_id->getDuid();
+ }
+ if (hwaddr && !hwaddr->hwaddr_.empty()) {
+ htype_ = hwaddr->htype_;
+ hwaddr_ = hwaddr->hwaddr_;
+ }
+}
+
+mutex ClientHandler::mutex_;
+
+ClientHandler::ClientByIdContainer ClientHandler::clients_client_id_;
+
+ClientHandler::ClientByHWAddrContainer ClientHandler::clients_hwaddr_;
+
+ClientHandler::ClientPtr
+ClientHandler::lookup(const DuidPtr& duid) {
+ // Sanity check.
+ if (!duid) {
+ isc_throw(InvalidParameter, "null duid in ClientHandler::lookup");
+ }
+
+ auto it = clients_client_id_.find(duid->getDuid());
+ if (it == clients_client_id_.end()) {
+ return (ClientPtr());
+ }
+ return (*it);
+}
+
+ClientHandler::ClientPtr
+ClientHandler::lookup(const HWAddrPtr& hwaddr) {
+ // Sanity checks.
+ if (!hwaddr) {
+ isc_throw(InvalidParameter, "null hwaddr in ClientHandler::lookup");
+ }
+ if (hwaddr->hwaddr_.empty()) {
+ isc_throw(InvalidParameter, "empty hwaddr in ClientHandler::lookup");
+ }
+
+ auto key = boost::make_tuple(hwaddr->htype_, hwaddr->hwaddr_);
+ auto it = clients_hwaddr_.find(key);
+ if (it == clients_hwaddr_.end()) {
+ return (ClientPtr());
+ }
+ return (*it);
+}
+
+void
+ClientHandler::addById(const ClientPtr& client) {
+ // Sanity check.
+ if (!client) {
+ isc_throw(InvalidParameter, "null client in ClientHandler::addById");
+ }
+
+ // Assume insert will never fail so not checking its result.
+ clients_client_id_.insert(client);
+}
+
+void
+ClientHandler::addByHWAddr(const ClientPtr& client) {
+ // Sanity check.
+ if (!client) {
+ isc_throw(InvalidParameter,
+ "null client in ClientHandler::addByHWAddr");
+ }
+
+ // Assume insert will never fail so not checking its result.
+ clients_hwaddr_.insert(client);
+}
+
+void
+ClientHandler::del(const DuidPtr& duid) {
+ // Sanity check.
+ if (!duid) {
+ isc_throw(InvalidParameter, "null duid in ClientHandler::del");
+ }
+
+ // Assume erase will never fail so not checking its result.
+ clients_client_id_.erase(duid->getDuid());
+}
+
+void
+ClientHandler::del(const HWAddrPtr& hwaddr) {
+ // Sanity checks.
+ if (!hwaddr) {
+ isc_throw(InvalidParameter, "null hwaddr in ClientHandler::del");
+ }
+ if (hwaddr->hwaddr_.empty()) {
+ isc_throw(InvalidParameter, "empty hwaddr in ClientHandler::del");
+ }
+
+ auto key = boost::make_tuple(hwaddr->htype_, hwaddr->hwaddr_);
+ // Assume erase will never fail so not checking its result.
+ auto it = clients_hwaddr_.find(key);
+ if (it == clients_hwaddr_.end()) {
+ // Should not happen.
+ return;
+ }
+ clients_hwaddr_.erase(it);
+}
+
+ClientHandler::ClientHandler()
+ : client_(), locked_client_id_(), locked_hwaddr_() {
+}
+
+ClientHandler::~ClientHandler() {
+ bool unlocked = false;
+ lock_guard<mutex> lk(mutex_);
+ if (locked_client_id_) {
+ unlocked = true;
+ unLockById();
+ }
+ if (locked_hwaddr_) {
+ unlocked = true;
+ unLockByHWAddr();
+ }
+ if (!unlocked || !client_ || !client_->cont_) {
+ return;
+ }
+ // Try to process next query. As the caller holds the mutex of
+ // the handler class the continuation will be resumed after.
+ MultiThreadingMgr& mt_mgr = MultiThreadingMgr::instance();
+ if (mt_mgr.getMode()) {
+ if (!mt_mgr.getThreadPool().addFront(client_->cont_)) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_PACKET_QUEUE_FULL);
+ }
+ }
+}
+
+bool
+ClientHandler::tryLock(Pkt4Ptr query, ContinuationPtr cont) {
+ // Sanity checks.
+ if (!query) {
+ isc_throw(InvalidParameter, "null query in ClientHandler::tryLock");
+ }
+ if (locked_client_id_) {
+ isc_throw(Unexpected,
+ "already handling client-id in ClientHandler::tryLock");
+ }
+ if (locked_hwaddr_) {
+ isc_throw(Unexpected,
+ "already handling hwaddr in ClientHandler::tryLock");
+ }
+
+ // Get identifiers.
+ OptionPtr opt_client_id = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ DuidPtr duid;
+ if (opt_client_id) {
+ duid.reset(new ClientId(opt_client_id->getData()));
+ }
+ HWAddrPtr hwaddr = query->getHWAddr();
+ if (hwaddr && hwaddr->hwaddr_.empty()) {
+ hwaddr.reset();
+ }
+ if (!duid && !hwaddr) {
+ // Can't do something useful: cross fingers.
+ return (true);
+ }
+
+ ClientPtr holder_id;
+ ClientPtr holder_hw;
+ Pkt4Ptr next_query_id;
+ Pkt4Ptr next_query_hw;
+ client_.reset(new Client(query, duid, hwaddr));
+
+ {
+ lock_guard<mutex> lk(mutex_);
+ // Try first duid.
+ if (duid) {
+ // Try to acquire the by-client-id lock and return the holder
+ // when it failed.
+ holder_id = lookup(duid);
+ if (!holder_id) {
+ locked_client_id_ = duid;
+ lockById();
+ } else if (cont) {
+ next_query_id = holder_id->next_query_;
+ holder_id->next_query_ = query;
+ holder_id->cont_ = cont;
+ }
+ }
+ if (!holder_id) {
+ if (!hwaddr) {
+ return (true);
+ }
+ // Try to acquire the by-hw-addr lock and return the holder
+ // when it failed.
+ holder_hw = lookup(hwaddr);
+ if (!holder_hw) {
+ locked_hwaddr_ = hwaddr;
+ lockByHWAddr();
+ return (true);
+ } else if (cont) {
+ next_query_hw = holder_hw->next_query_;
+ holder_hw->next_query_ = query;
+ holder_hw->cont_ = cont;
+ }
+ }
+ }
+
+ if (holder_id) {
+ // This query is a by-id duplicate so put the continuation.
+ if (cont) {
+ if (next_query_id) {
+ // Logging a warning as it is supposed to be a rare event
+ // with well behaving clients...
+ LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0011)
+ .arg(next_query_id->toText())
+ .arg(this_thread::get_id())
+ .arg(holder_id->query_->toText())
+ .arg(holder_id->thread_);
+ stats::StatsMgr::instance().addValue("pkt4-receive-drop",
+ static_cast<int64_t>(1));
+ }
+ } else {
+ // Logging a warning as it is supposed to be a rare event
+ // with well behaving clients...
+ LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0011)
+ .arg(query->toText())
+ .arg(this_thread::get_id())
+ .arg(holder_id->query_->toText())
+ .arg(holder_id->thread_);
+ stats::StatsMgr::instance().addValue("pkt4-receive-drop",
+ static_cast<int64_t>(1));
+ }
+ } else {
+ // This query is a by-hw duplicate so put the continuation.
+ if (cont) {
+ if (next_query_hw) {
+ // Logging a warning as it is supposed to be a rare event
+ // with well behaving clients...
+ LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0012)
+ .arg(next_query_hw->toText())
+ .arg(this_thread::get_id())
+ .arg(holder_hw->query_->toText())
+ .arg(holder_hw->thread_);
+ stats::StatsMgr::instance().addValue("pkt4-receive-drop",
+ static_cast<int64_t>(1));
+ }
+ } else {
+ // Logging a warning as it is supposed to be a rare event
+ // with well behaving clients...
+ LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0012)
+ .arg(query->toText())
+ .arg(this_thread::get_id())
+ .arg(holder_hw->query_->toText())
+ .arg(holder_hw->thread_);
+ stats::StatsMgr::instance().addValue("pkt4-receive-drop",
+ static_cast<int64_t>(1));
+ }
+ }
+ return (false);
+}
+
+void
+ClientHandler::lockById() {
+ // Sanity check.
+ if (!locked_client_id_) {
+ isc_throw(Unexpected, "nothing to lock in ClientHandler::lockById");
+ }
+
+ addById(client_);
+}
+
+void
+ClientHandler::lockByHWAddr() {
+ // Sanity check.
+ if (!locked_hwaddr_) {
+ isc_throw(Unexpected,
+ "nothing to lock in ClientHandler::lockByHWAddr");
+ }
+
+ addByHWAddr(client_);
+}
+
+void
+ClientHandler::unLockById() {
+ // Sanity check.
+ if (!locked_client_id_) {
+ isc_throw(Unexpected,
+ "nothing to unlock in ClientHandler::unLockById");
+ }
+
+ del(locked_client_id_);
+ locked_client_id_.reset();
+}
+
+void
+ClientHandler::unLockByHWAddr() {
+ // Sanity check.
+ if (!locked_hwaddr_) {
+ isc_throw(Unexpected,
+ "nothing to unlock in ClientHandler::unLockByHWAddr");
+ }
+
+ del(locked_hwaddr_);
+ locked_hwaddr_.reset();
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/bin/dhcp4/client_handler.h b/src/bin/dhcp4/client_handler.h
new file mode 100644
index 0000000..9b2b0c0
--- /dev/null
+++ b/src/bin/dhcp4/client_handler.h
@@ -0,0 +1,248 @@
+// Copyright (C) 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 CLIENT_HANDLER_H
+#define CLIENT_HANDLER_H
+
+#include <dhcp/pkt4.h>
+#include <boost/noncopyable.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/composite_key.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/member.hpp>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+#include <mutex>
+#include <thread>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Define the type of packet processing continuation.
+typedef std::function<void()> Continuation;
+
+/// @brief Define the type of shared pointers to continuations.
+typedef boost::shared_ptr<Continuation> ContinuationPtr;
+
+/// @brief Continuation factory.
+///
+/// @param cont Continuation rvalue.
+inline ContinuationPtr makeContinuation(Continuation&& cont) {
+ return (boost::make_shared<Continuation>(cont));
+}
+
+/// @brief Client race avoidance RAII handler.
+class ClientHandler : public boost::noncopyable {
+private:
+
+ /// Class (aka static) types, methods and members.
+
+ /// @brief Structure representing a client.
+ struct Client {
+
+ /// @brief Constructor.
+ ///
+ /// @param query The query.
+ /// @param client_id The client ID.
+ /// @param hwaddr The hardware address.
+ /// @throw if the query is null or client_id and hwaddr are null.
+ Client(Pkt4Ptr query, DuidPtr client_id, HWAddrPtr hwaddr);
+
+ /// @brief The query being processed.
+ Pkt4Ptr query_;
+
+ /// @brief Cached binary client ID.
+ std::vector<uint8_t> duid_;
+
+ /// @brief Cached hardware type.
+ uint16_t htype_;
+
+ /// @brief Cached binary hardware address.
+ std::vector<uint8_t> hwaddr_;
+
+ /// @brief The ID of the thread processing the query.
+ std::thread::id thread_;
+
+ /// @brief The next query.
+ ///
+ /// @note This field can be modified from another handler
+ /// holding the mutex.
+ Pkt4Ptr next_query_;
+
+ /// @brief The continuation to process next query for the client.
+ ///
+ /// @note This field can be modified from another handler
+ /// holding the mutex.
+ ContinuationPtr cont_;
+ };
+
+ /// @brief The type of shared pointers to clients.
+ typedef boost::shared_ptr<Client> ClientPtr;
+
+ /// @brief The type of the client-by-id container.
+ typedef boost::multi_index_container<
+
+ // This container stores pointers to client-by-id objects.
+ ClientPtr,
+
+ // Start specification of indexes here.
+ boost::multi_index::indexed_by<
+
+ // First index is used to search by Duid.
+ boost::multi_index::hashed_unique<
+
+ // Client ID binary content as a member of the Client object.
+ boost::multi_index::member<
+ Client, std::vector<uint8_t>, &Client::duid_
+ >
+ >
+ >
+ > ClientByIdContainer;
+
+ /// @brief The type of the client-by-hwaddr container.
+ typedef boost::multi_index_container<
+
+ // This container stores pointers to client-by-hwaddr objects.
+ ClientPtr,
+
+ // Start specification of indexes here.
+ boost::multi_index::indexed_by<
+
+ // First index is used to search by HWAddr.
+ boost::multi_index::hashed_unique<
+
+ // This is a composite index for type and binary components.
+ boost::multi_index::composite_key<
+ Client,
+ boost::multi_index::member<
+ Client, uint16_t, &Client::htype_
+ >,
+ boost::multi_index::member<
+ Client, std::vector<uint8_t>, &Client::hwaddr_
+ >
+ >
+ >
+ >
+ > ClientByHWAddrContainer;
+
+ /// @brief Lookup a client by id.
+ ///
+ /// The mutex must be held by the caller.
+ ///
+ /// @param duid The duid of the query from the client.
+ /// @return The client found in the by client id container or null.
+ static ClientPtr lookup(const DuidPtr& duid);
+
+ /// @brief Lookup a client by hwaddr.
+ ///
+ /// The mutex must be held by the caller.
+ ///
+ /// @param hwaddr The hardware address of the query from the client.
+ /// @return The client found in the by hardware address container or null.
+ static ClientPtr lookup(const HWAddrPtr& hwaddr);
+
+ /// @brief Add a client by id.
+ ///
+ /// The mutex must be held by the caller.
+ ///
+ /// @param client The client to insert into the by id client container.
+ static void addById(const ClientPtr& client);
+
+ /// @brief Add a client by hwaddr.
+ ///
+ /// The mutex must be held by the caller.
+ ///
+ /// @param client The client to insert into the by hwaddr client container.
+ static void addByHWAddr(const ClientPtr& client);
+
+ /// @brief Delete a client by id.
+ ///
+ /// The mutex must be held by the caller.
+ ///
+ /// @param duid The duid to delete from the by id client container.
+ static void del(const DuidPtr& duid);
+
+ /// @brief Delete a client by hwaddr.
+ ///
+ /// The mutex must be held by the caller.
+ ///
+ /// @param hwaddr The hwaddr to delete from the by hwaddr client container.
+ static void del(const HWAddrPtr& hwaddr);
+
+ /// @brief Mutex to protect client containers.
+ ///
+ /// The mutex is used only by public methods for guards.
+ static std::mutex mutex_;
+
+ /// @brief The client-by-id container.
+ static ClientByIdContainer clients_client_id_;
+
+ /// @brief The client-by-hwaddr container.
+ static ClientByHWAddrContainer clients_hwaddr_;
+
+public:
+
+ /// Public interface.
+
+ /// @brief Constructor.
+ ClientHandler();
+
+ /// @brief Destructor.
+ ///
+ /// Releases the client if it was acquired.
+ virtual ~ClientHandler();
+
+ /// @brief Tries to acquires a client.
+ ///
+ /// Lookup the client:
+ /// - if not found insert the client in the clients map and return true
+ /// - if found, if has a continuation put it in the holder,
+ /// and return false
+ ///
+ /// @param query The query from the client.
+ /// @param cont The continuation in the case the client was held.
+ /// @return true if the client was acquired, false if there is already
+ /// a query from the same client.
+ bool tryLock(Pkt4Ptr query, ContinuationPtr cont = ContinuationPtr());
+
+private:
+
+ /// Instance methods and members.
+
+ /// @brief Acquire a client by client ID option.
+ ///
+ /// The mutex must be held by the caller.
+ void lockById();
+
+ /// @brief Acquire a client by hardware address.
+ ///
+ /// The mutex must be held by the caller.
+ void lockByHWAddr();
+
+ /// @brief Release a client by client ID option.
+ ///
+ /// The mutex must be held by the caller.
+ void unLockById();
+
+ /// @brief Release a client by hardware address.
+ ///
+ /// The mutex must be held by the caller.
+ void unLockByHWAddr();
+
+ /// @brief Local client.
+ ClientPtr client_;
+
+ /// @brief Client ID locked by this handler.
+ DuidPtr locked_client_id_;
+
+ /// @brief Hardware address locked by this handler.
+ HWAddrPtr locked_hwaddr_;
+};
+
+} // namespace isc
+} // namespace dhcp
+
+#endif // CLIENT_HANDLER_H
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
new file mode 100644
index 0000000..bddf8ae
--- /dev/null
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
@@ -0,0 +1,1394 @@
+// Copyright (C) 2014-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 <cfgrpt/config_report.h>
+#include <config/command_mgr.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/dhcp4_log.h>
+#include <dhcp4/dhcp4to6_ipc.h>
+#include <dhcp4/json_config_parser.h>
+#include <dhcp4/parser_context.h>
+#include <dhcpsrv/cfg_db_access.h>
+#include <dhcpsrv/cfg_multi_threading.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/db_type.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <hooks/hooks.h>
+#include <hooks/hooks_manager.h>
+#include <stats/stats_mgr.h>
+#include <util/multi_threading_mgr.h>
+
+#include <signal.h>
+
+#include <sstream>
+
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::db;
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::stats;
+using namespace isc::util;
+using namespace std;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// Structure that holds registered hook indexes.
+struct CtrlDhcp4Hooks {
+ int hooks_index_dhcp4_srv_configured_;
+
+ /// Constructor that registers hook points for the DHCPv4 server.
+ CtrlDhcp4Hooks() {
+ hooks_index_dhcp4_srv_configured_ = HooksManager::registerHook("dhcp4_srv_configured");
+ }
+
+};
+
+// Declare a Hooks object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+CtrlDhcp4Hooks Hooks;
+
+/// @brief Signals handler for DHCPv4 server.
+///
+/// This signal handler handles the following signals received by the DHCPv4
+/// server process:
+/// - SIGHUP - triggers server's dynamic reconfiguration.
+/// - SIGTERM - triggers server's shut down.
+/// - SIGINT - triggers server's shut down.
+///
+/// @param signo Signal number received.
+void signalHandler(int signo) {
+ // SIGHUP signals a request to reconfigure the server.
+ if (signo == SIGHUP) {
+ ControlledDhcpv4Srv::processCommand("config-reload",
+ ConstElementPtr());
+ } else if ((signo == SIGTERM) || (signo == SIGINT)) {
+ ControlledDhcpv4Srv::processCommand("shutdown",
+ ConstElementPtr());
+ }
+}
+
+}
+
+namespace isc {
+namespace dhcp {
+
+ControlledDhcpv4Srv* ControlledDhcpv4Srv::server_ = NULL;
+
+void
+ControlledDhcpv4Srv::init(const std::string& file_name) {
+ // Keep the call timestamp.
+ start_ = boost::posix_time::second_clock::universal_time();
+
+ // Configure the server using JSON file.
+ ConstElementPtr result = loadConfigFile(file_name);
+
+ int rcode;
+ ConstElementPtr comment = isc::config::parseAnswer(rcode, result);
+ if (rcode != CONTROL_RESULT_SUCCESS) {
+ string reason = comment ? comment->stringValue() :
+ "no details available";
+ isc_throw(isc::BadValue, reason);
+ }
+
+ // We don't need to call openActiveSockets() or startD2() as these
+ // methods are called in processConfig() which is called by
+ // processCommand("config-set", ...)
+
+ // Set signal handlers. When the SIGHUP is received by the process
+ // the server reconfiguration will be triggered. When SIGTERM or
+ // SIGINT will be received, the server will start shutting down.
+ signal_set_.reset(new IOSignalSet(getIOService(), signalHandler));
+
+ signal_set_->add(SIGINT);
+ signal_set_->add(SIGHUP);
+ signal_set_->add(SIGTERM);
+}
+
+void ControlledDhcpv4Srv::cleanup() {
+ // Nothing to do here. No need to disconnect from anything.
+}
+
+ConstElementPtr
+ControlledDhcpv4Srv::loadConfigFile(const std::string& file_name) {
+ // This is a configuration backend implementation that reads the
+ // configuration from a JSON file.
+
+ isc::data::ConstElementPtr json;
+ isc::data::ConstElementPtr result;
+
+ // Basic sanity check: file name must not be empty.
+ try {
+ if (file_name.empty()) {
+ // Basic sanity check: file name must not be empty.
+ isc_throw(isc::BadValue, "JSON configuration file not specified."
+ " Please use -c command line option.");
+ }
+
+ // Read contents of the file and parse it as JSON
+ Parser4Context parser;
+ json = parser.parseFile(file_name, Parser4Context::PARSER_DHCP4);
+ if (!json) {
+ isc_throw(isc::BadValue, "no configuration found");
+ }
+
+ // Let's do sanity check before we call json->get() which
+ // works only for map.
+ if (json->getType() != isc::data::Element::map) {
+ isc_throw(isc::BadValue, "Configuration file is expected to be "
+ "a map, i.e., start with { and end with } and contain "
+ "at least an entry called 'Dhcp4' that itself is a map. "
+ << file_name
+ << " is a valid JSON, but its top element is not a map."
+ " Did you forget to add { } around your configuration?");
+ }
+
+ // Use parsed JSON structures to configure the server
+ result = ControlledDhcpv4Srv::processCommand("config-set", json);
+ if (!result) {
+ // Undetermined status of the configuration. This should never
+ // happen, but as the configureDhcp4Server returns a pointer, it is
+ // theoretically possible that it will return NULL.
+ isc_throw(isc::BadValue, "undefined result of "
+ "processCommand(\"config-set\", json)");
+ }
+
+ // Now check is the returned result is successful (rcode=0) or not
+ // (see @ref isc::config::parseAnswer).
+ int rcode;
+ ConstElementPtr comment = isc::config::parseAnswer(rcode, result);
+ if (rcode != CONTROL_RESULT_SUCCESS) {
+ string reason = comment ? comment->stringValue() :
+ "no details available";
+ isc_throw(isc::BadValue, reason);
+ }
+ } catch (const std::exception& ex) {
+ // If configuration failed at any stage, we drop the staging
+ // configuration and continue to use the previous one.
+ CfgMgr::instance().rollback();
+
+ LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
+ .arg(file_name).arg(ex.what());
+ isc_throw(isc::BadValue, "configuration error using file '"
+ << file_name << "': " << ex.what());
+ }
+
+ LOG_WARN(dhcp4_logger, DHCP4_MULTI_THREADING_INFO)
+ .arg(MultiThreadingMgr::instance().getMode() ? "yes" : "no")
+ .arg(MultiThreadingMgr::instance().getThreadPoolSize())
+ .arg(MultiThreadingMgr::instance().getPacketQueueSize());
+
+ return (result);
+}
+
+ConstElementPtr
+ControlledDhcpv4Srv::commandShutdownHandler(const string&, ConstElementPtr args) {
+ if (!ControlledDhcpv4Srv::getInstance()) {
+ LOG_WARN(dhcp4_logger, DHCP4_NOT_RUNNING);
+ return (createAnswer(CONTROL_RESULT_ERROR, "Shutdown failure."));
+ }
+
+ int exit_value = 0;
+ if (args) {
+ // @todo Should we go ahead and shutdown even if the args are invalid?
+ if (args->getType() != Element::map) {
+ return (createAnswer(CONTROL_RESULT_ERROR, "Argument must be a map"));
+ }
+
+ ConstElementPtr param = args->get("exit-value");
+ if (param) {
+ if (param->getType() != Element::integer) {
+ return (createAnswer(CONTROL_RESULT_ERROR,
+ "parameter 'exit-value' is not an integer"));
+ }
+
+ exit_value = param->intValue();
+ }
+ }
+
+ ControlledDhcpv4Srv::getInstance()->shutdownServer(exit_value);
+ return (createAnswer(CONTROL_RESULT_SUCCESS, "Shutting down."));
+}
+
+ConstElementPtr
+ControlledDhcpv4Srv::commandLibReloadHandler(const string&, ConstElementPtr) {
+ // stop thread pool (if running)
+ MultiThreadingCriticalSection cs;
+
+ // Clear the packet queue.
+ MultiThreadingMgr::instance().getThreadPool().reset();
+
+ try {
+ /// Get list of currently loaded libraries and reload them.
+ HookLibsCollection loaded = HooksManager::getLibraryInfo();
+ HooksManager::prepareUnloadLibraries();
+ static_cast<void>(HooksManager::unloadLibraries());
+ bool status = HooksManager::loadLibraries(loaded);
+ if (!status) {
+ isc_throw(Unexpected, "Failed to reload hooks libraries.");
+ }
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcp4_logger, DHCP4_HOOKS_LIBS_RELOAD_FAIL);
+ ConstElementPtr answer = isc::config::createAnswer(1, ex.what());
+ return (answer);
+ }
+ ConstElementPtr answer = isc::config::createAnswer(0,
+ "Hooks libraries successfully reloaded.");
+ return (answer);
+}
+
+ConstElementPtr
+ControlledDhcpv4Srv::commandConfigReloadHandler(const string&,
+ ConstElementPtr /*args*/) {
+ // Get configuration file name.
+ std::string file = ControlledDhcpv4Srv::getInstance()->getConfigFile();
+ try {
+ LOG_INFO(dhcp4_logger, DHCP4_DYNAMIC_RECONFIGURATION).arg(file);
+ auto result = loadConfigFile(file);
+ LOG_INFO(dhcp4_logger, DHCP4_DYNAMIC_RECONFIGURATION_SUCCESS).arg(file);
+ return (result);
+ } catch (const std::exception& ex) {
+ // Log the unsuccessful reconfiguration. The reason for failure
+ // should be already logged. Don't rethrow an exception so as
+ // the server keeps working.
+ LOG_FATAL(dhcp4_logger, DHCP4_DYNAMIC_RECONFIGURATION_FAIL)
+ .arg(file);
+ return (createAnswer(CONTROL_RESULT_ERROR,
+ "Config reload failed: " + string(ex.what())));
+ }
+}
+
+ConstElementPtr
+ControlledDhcpv4Srv::commandConfigGetHandler(const string&,
+ ConstElementPtr /*args*/) {
+ ConstElementPtr config = CfgMgr::instance().getCurrentCfg()->toElement();
+
+ return (createAnswer(0, config));
+}
+
+ConstElementPtr
+ControlledDhcpv4Srv::commandConfigWriteHandler(const string&,
+ ConstElementPtr args) {
+ string filename;
+
+ if (args) {
+ if (args->getType() != Element::map) {
+ return (createAnswer(CONTROL_RESULT_ERROR, "Argument must be a map"));
+ }
+ ConstElementPtr filename_param = args->get("filename");
+ if (filename_param) {
+ if (filename_param->getType() != Element::string) {
+ return (createAnswer(CONTROL_RESULT_ERROR,
+ "passed parameter 'filename' is not a string"));
+ }
+ filename = filename_param->stringValue();
+ }
+ }
+
+ if (filename.empty()) {
+ // filename parameter was not specified, so let's use whatever we remember
+ // from the command-line
+ filename = getConfigFile();
+ }
+
+ if (filename.empty()) {
+ return (createAnswer(CONTROL_RESULT_ERROR, "Unable to determine filename."
+ "Please specify filename explicitly."));
+ }
+
+ // Ok, it's time to write the file.
+ size_t size = 0;
+ try {
+ ConstElementPtr cfg = CfgMgr::instance().getCurrentCfg()->toElement();
+ size = writeConfigFile(filename, cfg);
+ } catch (const isc::Exception& ex) {
+ return (createAnswer(CONTROL_RESULT_ERROR, string("Error during write-config:")
+ + ex.what()));
+ }
+ if (size == 0) {
+ return (createAnswer(CONTROL_RESULT_ERROR, "Error writing configuration to "
+ + filename));
+ }
+
+ // Ok, it's time to return the successful response.
+ ElementPtr params = Element::createMap();
+ params->set("size", Element::create(static_cast<long long>(size)));
+ params->set("filename", Element::create(filename));
+
+ return (createAnswer(CONTROL_RESULT_SUCCESS, "Configuration written to "
+ + filename + " successful", params));
+}
+
+ConstElementPtr
+ControlledDhcpv4Srv::commandConfigSetHandler(const string&,
+ ConstElementPtr args) {
+ const int status_code = CONTROL_RESULT_ERROR;
+ ConstElementPtr dhcp4;
+ string message;
+
+ // Command arguments are expected to be:
+ // { "Dhcp4": { ... } }
+ if (!args) {
+ message = "Missing mandatory 'arguments' parameter.";
+ } else {
+ dhcp4 = args->get("Dhcp4");
+ if (!dhcp4) {
+ message = "Missing mandatory 'Dhcp4' parameter.";
+ } else if (dhcp4->getType() != Element::map) {
+ message = "'Dhcp4' parameter expected to be a map.";
+ }
+ }
+
+ // Check unsupported objects.
+ if (message.empty()) {
+ for (auto obj : args->mapValue()) {
+ const string& obj_name = obj.first;
+ if (obj_name != "Dhcp4") {
+ LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_UNSUPPORTED_OBJECT)
+ .arg(obj_name);
+ if (message.empty()) {
+ message = "Unsupported '" + obj_name + "' parameter";
+ } else {
+ message += " (and '" + obj_name + "')";
+ }
+ }
+ }
+ if (!message.empty()) {
+ message += ".";
+ }
+ }
+
+ if (!message.empty()) {
+ // Something is amiss with arguments, return a failure response.
+ ConstElementPtr result = isc::config::createAnswer(status_code,
+ message);
+ return (result);
+ }
+
+ // stop thread pool (if running)
+ MultiThreadingCriticalSection cs;
+
+ // disable multi-threading (it will be applied by new configuration)
+ // this must be done in order to properly handle MT to ST transition
+ // when 'multi-threading' structure is missing from new config
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+
+ // We are starting the configuration process so we should remove any
+ // staging configuration that has been created during previous
+ // configuration attempts.
+ CfgMgr::instance().rollback();
+
+ // 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.
+ Daemon::configureLogger(dhcp4, 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();
+
+ // Now we configure the server proper.
+ ConstElementPtr result = processConfig(dhcp4);
+
+ // If the configuration parsed successfully, apply the new logger
+ // configuration and the commit the new configuration. We apply
+ // the logging first in case there's a configuration failure.
+ int rcode = 0;
+ isc::config::parseAnswer(rcode, result);
+ if (rcode == CONTROL_RESULT_SUCCESS) {
+ CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
+
+ // Use new configuration.
+ CfgMgr::instance().commit();
+ } else {
+ // Ok, we applied the logging from the upcoming configuration, but
+ // there were problems with the config. As such, we need to back off
+ // and revert to the previous logging configuration.
+ CfgMgr::instance().getCurrentCfg()->applyLoggingCfg();
+
+ if (CfgMgr::instance().getCurrentCfg()->getSequence() != 0) {
+ // Not initial configuration so someone can believe we reverted
+ // to the previous configuration. It is not the case so be clear
+ // about this.
+ LOG_FATAL(dhcp4_logger, DHCP4_CONFIG_UNRECOVERABLE_ERROR);
+ }
+ }
+
+ return (result);
+}
+
+ConstElementPtr
+ControlledDhcpv4Srv::commandConfigTestHandler(const string&,
+ ConstElementPtr args) {
+ const int status_code = CONTROL_RESULT_ERROR; // 1 indicates an error
+ ConstElementPtr dhcp4;
+ string message;
+
+ // Command arguments are expected to be:
+ // { "Dhcp4": { ... } }
+ if (!args) {
+ message = "Missing mandatory 'arguments' parameter.";
+ } else {
+ dhcp4 = args->get("Dhcp4");
+ if (!dhcp4) {
+ message = "Missing mandatory 'Dhcp4' parameter.";
+ } else if (dhcp4->getType() != Element::map) {
+ message = "'Dhcp4' parameter expected to be a map.";
+ }
+ }
+
+ // Check unsupported objects.
+ if (message.empty()) {
+ for (auto obj : args->mapValue()) {
+ const string& obj_name = obj.first;
+ if (obj_name != "Dhcp4") {
+ LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_UNSUPPORTED_OBJECT)
+ .arg(obj_name);
+ if (message.empty()) {
+ message = "Unsupported '" + obj_name + "' parameter";
+ } else {
+ message += " (and '" + obj_name + "')";
+ }
+ }
+ }
+ if (!message.empty()) {
+ message += ".";
+ }
+ }
+
+ if (!message.empty()) {
+ // Something is amiss with arguments, return a failure response.
+ ConstElementPtr result = isc::config::createAnswer(status_code,
+ message);
+ return (result);
+ }
+
+ // stop thread pool (if running)
+ MultiThreadingCriticalSection cs;
+
+ // We are starting the configuration process so we should remove any
+ // staging configuration that has been created during previous
+ // configuration attempts.
+ CfgMgr::instance().rollback();
+
+ // Now we check the server proper.
+ return (checkConfig(dhcp4));
+}
+
+ConstElementPtr
+ControlledDhcpv4Srv::commandDhcpDisableHandler(const std::string&,
+ ConstElementPtr args) {
+ std::ostringstream message;
+ int64_t max_period = 0;
+ std::string origin;
+
+ // If the args map does not contain 'origin' parameter, the default type
+ // will be used (user command).
+ NetworkState::Origin type = NetworkState::Origin::USER_COMMAND;
+
+ // Parse arguments to see if the 'max-period' or 'origin' parameters have
+ // been specified.
+ if (args) {
+ // Arguments must be a map.
+ if (args->getType() != Element::map) {
+ message << "arguments for the 'dhcp-disable' command must be a map";
+
+ } else {
+ ConstElementPtr max_period_element = args->get("max-period");
+ // max-period is optional.
+ if (max_period_element) {
+ // It must be an integer, if specified.
+ if (max_period_element->getType() != Element::integer) {
+ message << "'max-period' argument must be a number";
+
+ } else {
+ // It must be positive integer.
+ max_period = max_period_element->intValue();
+ if (max_period <= 0) {
+ message << "'max-period' must be positive integer";
+ }
+ }
+ }
+ ConstElementPtr origin_element = args->get("origin");
+ // The 'origin' parameter is optional.
+ if (origin_element) {
+ // It must be a string, if specified.
+ if (origin_element->getType() != Element::string) {
+ message << "'origin' argument must be a string";
+
+ } else {
+ origin = origin_element->stringValue();
+ if (origin == "ha-partner") {
+ type = NetworkState::Origin::HA_COMMAND;
+ } else if (origin != "user") {
+ if (origin.empty()) {
+ origin = "(empty string)";
+ }
+ message << "invalid value used for 'origin' parameter: "
+ << origin;
+ }
+ }
+ }
+ }
+ }
+
+ // No error occurred, so let's disable the service.
+ if (message.tellp() == 0) {
+ message << "DHCPv4 service disabled";
+ if (max_period > 0) {
+ message << " for " << max_period << " seconds";
+
+ // The user specified that the DHCP service should resume not
+ // later than in max-period seconds. If the 'dhcp-enable' command
+ // is not sent, the DHCP service will resume automatically.
+ network_state_->delayedEnableAll(static_cast<unsigned>(max_period),
+ type);
+ }
+ network_state_->disableService(type);
+
+ // Success.
+ return (config::createAnswer(CONTROL_RESULT_SUCCESS, message.str()));
+ }
+
+ // Failure.
+ return (config::createAnswer(CONTROL_RESULT_ERROR, message.str()));
+}
+
+ConstElementPtr
+ControlledDhcpv4Srv::commandDhcpEnableHandler(const std::string&,
+ ConstElementPtr args) {
+ std::ostringstream message;
+ std::string origin;
+
+ // If the args map does not contain 'origin' parameter, the default type
+ // will be used (user command).
+ NetworkState::Origin type = NetworkState::Origin::USER_COMMAND;
+
+ // Parse arguments to see if the 'origin' parameter has been specified.
+ if (args) {
+ // Arguments must be a map.
+ if (args->getType() != Element::map) {
+ message << "arguments for the 'dhcp-enable' command must be a map";
+
+ } else {
+ ConstElementPtr origin_element = args->get("origin");
+ // The 'origin' parameter is optional.
+ if (origin_element) {
+ // It must be a string, if specified.
+ if (origin_element->getType() != Element::string) {
+ message << "'origin' argument must be a string";
+
+ } else {
+ origin = origin_element->stringValue();
+ if (origin == "ha-partner") {
+ type = NetworkState::Origin::HA_COMMAND;
+ } else if (origin != "user") {
+ if (origin.empty()) {
+ origin = "(empty string)";
+ }
+ message << "invalid value used for 'origin' parameter: "
+ << origin;
+ }
+ }
+ }
+ }
+ }
+
+ // No error occurred, so let's enable the service.
+ if (message.tellp() == 0) {
+ network_state_->enableService(type);
+
+ // Success.
+ return (config::createAnswer(CONTROL_RESULT_SUCCESS,
+ "DHCP service successfully enabled"));
+ }
+
+ // Failure.
+ return (config::createAnswer(CONTROL_RESULT_ERROR, message.str()));
+}
+
+ConstElementPtr
+ControlledDhcpv4Srv::commandVersionGetHandler(const string&, ConstElementPtr) {
+ ElementPtr extended = Element::create(Dhcpv4Srv::getVersion(true));
+ ElementPtr arguments = Element::createMap();
+ arguments->set("extended", extended);
+ ConstElementPtr answer = isc::config::createAnswer(0,
+ Dhcpv4Srv::getVersion(false),
+ arguments);
+ return (answer);
+}
+
+ConstElementPtr
+ControlledDhcpv4Srv::commandBuildReportHandler(const string&,
+ ConstElementPtr) {
+ ConstElementPtr answer =
+ isc::config::createAnswer(0, isc::detail::getConfigReport());
+ return (answer);
+}
+
+ConstElementPtr
+ControlledDhcpv4Srv::commandLeasesReclaimHandler(const string&,
+ ConstElementPtr args) {
+ int status_code = CONTROL_RESULT_ERROR;
+ string message;
+
+ // args must be { "remove": <bool> }
+ if (!args) {
+ message = "Missing mandatory 'remove' parameter.";
+ } else {
+ ConstElementPtr remove_name = args->get("remove");
+ if (!remove_name) {
+ message = "Missing mandatory 'remove' parameter.";
+ } else if (remove_name->getType() != Element::boolean) {
+ message = "'remove' parameter expected to be a boolean.";
+ } else {
+ bool remove_lease = remove_name->boolValue();
+ server_->alloc_engine_->reclaimExpiredLeases4(0, 0, remove_lease);
+ status_code = 0;
+ message = "Reclamation of expired leases is complete.";
+ }
+ }
+ ConstElementPtr answer = isc::config::createAnswer(status_code, message);
+ return (answer);
+}
+
+ConstElementPtr
+ControlledDhcpv4Srv::commandServerTagGetHandler(const std::string&,
+ ConstElementPtr) {
+ const std::string& tag =
+ CfgMgr::instance().getCurrentCfg()->getServerTag();
+ ElementPtr response = Element::createMap();
+ response->set("server-tag", Element::create(tag));
+
+ return (createAnswer(CONTROL_RESULT_SUCCESS, response));
+}
+
+ConstElementPtr
+ControlledDhcpv4Srv::commandConfigBackendPullHandler(const std::string&,
+ ConstElementPtr) {
+ auto ctl_info = CfgMgr::instance().getCurrentCfg()->getConfigControlInfo();
+ if (!ctl_info) {
+ return (createAnswer(CONTROL_RESULT_EMPTY, "No config backend."));
+ }
+
+ // stop thread pool (if running)
+ MultiThreadingCriticalSection cs;
+
+ // Reschedule the periodic CB fetch.
+ if (TimerMgr::instance()->isTimerRegistered("Dhcp4CBFetchTimer")) {
+ TimerMgr::instance()->cancel("Dhcp4CBFetchTimer");
+ TimerMgr::instance()->setup("Dhcp4CBFetchTimer");
+ }
+
+ // Code from cbFetchUpdates.
+ // The configuration to use is the current one because this is called
+ // after the configuration manager commit.
+ try {
+ auto srv_cfg = CfgMgr::instance().getCurrentCfg();
+ auto mode = CBControlDHCPv4::FetchMode::FETCH_UPDATE;
+ server_->getCBControl()->databaseConfigFetch(srv_cfg, mode);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcp4_logger, DHCP4_CB_ON_DEMAND_FETCH_UPDATES_FAIL)
+ .arg(ex.what());
+ return (createAnswer(CONTROL_RESULT_ERROR,
+ "On demand configuration update failed: " +
+ string(ex.what())));
+ }
+ return (createAnswer(CONTROL_RESULT_SUCCESS,
+ "On demand configuration update successful."));
+}
+
+ConstElementPtr
+ControlledDhcpv4Srv::commandStatusGetHandler(const string&,
+ ConstElementPtr /*args*/) {
+ ElementPtr status = Element::createMap();
+ status->set("pid", Element::create(static_cast<int>(getpid())));
+
+ auto now = boost::posix_time::second_clock::universal_time();
+ // Sanity check: start_ is always initialized.
+ if (!start_.is_not_a_date_time()) {
+ auto uptime = now - start_;
+ status->set("uptime", Element::create(uptime.total_seconds()));
+ }
+
+ auto last_commit = CfgMgr::instance().getCurrentCfg()->getLastCommitTime();
+ if (!last_commit.is_not_a_date_time()) {
+ auto reload = now - last_commit;
+ status->set("reload", Element::create(reload.total_seconds()));
+ }
+
+ auto& mt_mgr = MultiThreadingMgr::instance();
+ if (mt_mgr.getMode()) {
+ status->set("multi-threading-enabled", Element::create(true));
+ status->set("thread-pool-size", Element::create(static_cast<int32_t>(
+ MultiThreadingMgr::instance().getThreadPoolSize())));
+ status->set("packet-queue-size", Element::create(static_cast<int32_t>(
+ MultiThreadingMgr::instance().getPacketQueueSize())));
+ ElementPtr queue_stats = Element::createList();
+ queue_stats->add(Element::create(mt_mgr.getThreadPool().getQueueStat(10)));
+ queue_stats->add(Element::create(mt_mgr.getThreadPool().getQueueStat(100)));
+ queue_stats->add(Element::create(mt_mgr.getThreadPool().getQueueStat(1000)));
+ status->set("packet-queue-statistics", queue_stats);
+
+ } else {
+ status->set("multi-threading-enabled", Element::create(false));
+ }
+
+ // Iterate through the interfaces and get all the errors.
+ ElementPtr socket_errors(Element::createList());
+ for (IfacePtr const& interface : IfaceMgr::instance().getIfaces()) {
+ for (std::string const& error : interface->getErrors()) {
+ socket_errors->add(Element::create(error));
+ }
+ }
+
+ // Abstract the information from all sockets into a single status.
+ ElementPtr sockets(Element::createMap());
+ if (socket_errors->empty()) {
+ sockets->set("status", Element::create("ready"));
+ } else {
+ ReconnectCtlPtr const reconnect_ctl(
+ CfgMgr::instance().getCurrentCfg()->getCfgIface()->getReconnectCtl());
+ if (reconnect_ctl && reconnect_ctl->retriesLeft()) {
+ sockets->set("status", Element::create("retrying"));
+ } else {
+ sockets->set("status", Element::create("failed"));
+ }
+ sockets->set("errors", socket_errors);
+ }
+ status->set("sockets", sockets);
+
+ return (createAnswer(0, status));
+}
+
+ConstElementPtr
+ControlledDhcpv4Srv::commandStatisticSetMaxSampleCountAllHandler(const string&,
+ ConstElementPtr args) {
+ StatsMgr& stats_mgr = StatsMgr::instance();
+ ConstElementPtr answer = stats_mgr.statisticSetMaxSampleCountAllHandler(args);
+ // Update the default parameter.
+ long max_samples = stats_mgr.getMaxSampleCountDefault();
+ CfgMgr::instance().getCurrentCfg()->addConfiguredGlobal(
+ "statistic-default-sample-count", Element::create(max_samples));
+ return (answer);
+}
+
+ConstElementPtr
+ControlledDhcpv4Srv::commandStatisticSetMaxSampleAgeAllHandler(const string&,
+ ConstElementPtr args) {
+ StatsMgr& stats_mgr = StatsMgr::instance();
+ ConstElementPtr answer = stats_mgr.statisticSetMaxSampleAgeAllHandler(args);
+ // Update the default parameter.
+ auto duration = stats_mgr.getMaxSampleAgeDefault();
+ long max_age = toSeconds(duration);
+ CfgMgr::instance().getCurrentCfg()->addConfiguredGlobal(
+ "statistic-default-sample-age", Element::create(max_age));
+ return (answer);
+}
+
+ConstElementPtr
+ControlledDhcpv4Srv::processCommand(const string& command,
+ ConstElementPtr args) {
+ string txt = args ? args->str() : "(none)";
+
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_COMMAND_RECEIVED)
+ .arg(command).arg(txt);
+
+ ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
+
+ if (!srv) {
+ ConstElementPtr no_srv = isc::config::createAnswer(1,
+ "Server object not initialized, so can't process command '" +
+ command + "', arguments: '" + txt + "'.");
+ return (no_srv);
+ }
+
+ try {
+ if (command == "shutdown") {
+ return (srv->commandShutdownHandler(command, args));
+
+ } else if (command == "libreload") {
+ return (srv->commandLibReloadHandler(command, args));
+
+ } else if (command == "config-reload") {
+ return (srv->commandConfigReloadHandler(command, args));
+
+ } else if (command == "config-set") {
+ return (srv->commandConfigSetHandler(command, args));
+
+ } else if (command == "config-get") {
+ return (srv->commandConfigGetHandler(command, args));
+
+ } else if (command == "config-test") {
+ return (srv->commandConfigTestHandler(command, args));
+
+ } else if (command == "dhcp-disable") {
+ return (srv->commandDhcpDisableHandler(command, args));
+
+ } else if (command == "dhcp-enable") {
+ return (srv->commandDhcpEnableHandler(command, args));
+
+ } else if (command == "version-get") {
+ return (srv->commandVersionGetHandler(command, args));
+
+ } else if (command == "build-report") {
+ return (srv->commandBuildReportHandler(command, args));
+
+ } else if (command == "leases-reclaim") {
+ return (srv->commandLeasesReclaimHandler(command, args));
+
+ } else if (command == "config-write") {
+ return (srv->commandConfigWriteHandler(command, args));
+
+ } else if (command == "server-tag-get") {
+ return (srv->commandServerTagGetHandler(command, args));
+
+ } else if (command == "config-backend-pull") {
+ return (srv->commandConfigBackendPullHandler(command, args));
+
+ } else if (command == "status-get") {
+ return (srv->commandStatusGetHandler(command, args));
+ }
+
+ return (isc::config::createAnswer(1, "Unrecognized command:"
+ + command));
+
+ } catch (const isc::Exception& ex) {
+ return (isc::config::createAnswer(1, "Error while processing command '"
+ + command + "':" + ex.what() +
+ ", params: '" + txt + "'"));
+ }
+}
+
+isc::data::ConstElementPtr
+ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
+ ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
+
+ // Single stream instance used in all error clauses
+ std::ostringstream err;
+
+ if (!srv) {
+ err << "Server object not initialized, can't process config.";
+ return (isc::config::createAnswer(1, err.str()));
+ }
+
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_RECEIVED)
+ .arg(srv->redactConfig(config)->str());
+
+ ConstElementPtr answer = configureDhcp4Server(*srv, config);
+
+ // Check that configuration was successful. If not, do not reopen sockets
+ // and don't bother with DDNS stuff.
+ try {
+ int rcode = 0;
+ isc::config::parseAnswer(rcode, answer);
+ if (rcode != 0) {
+ return (answer);
+ }
+ } catch (const std::exception& ex) {
+ err << "Failed to process configuration:" << ex.what();
+ return (isc::config::createAnswer(1, err.str()));
+ }
+
+ // Re-open lease and host database with new parameters.
+ try {
+ DatabaseConnection::db_lost_callback_ =
+ std::bind(&ControlledDhcpv4Srv::dbLostCallback, srv, ph::_1);
+
+ DatabaseConnection::db_recovered_callback_ =
+ std::bind(&ControlledDhcpv4Srv::dbRecoveredCallback, srv, ph::_1);
+
+ DatabaseConnection::db_failed_callback_ =
+ std::bind(&ControlledDhcpv4Srv::dbFailedCallback, srv, ph::_1);
+
+ CfgDbAccessPtr cfg_db = CfgMgr::instance().getStagingCfg()->getCfgDbAccess();
+ cfg_db->setAppendedParameters("universe=4");
+ cfg_db->createManagers();
+ // Reset counters related to connections as all managers have been recreated.
+ srv->getNetworkState()->reset(NetworkState::Origin::DB_CONNECTION);
+ } catch (const std::exception& ex) {
+ err << "Unable to open database: " << ex.what();
+ return (isc::config::createAnswer(1, err.str()));
+ }
+
+ // Server will start DDNS communications if its enabled.
+ try {
+ srv->startD2();
+ } catch (const std::exception& ex) {
+ err << "Error starting DHCP_DDNS client after server reconfiguration: "
+ << ex.what();
+ return (isc::config::createAnswer(1, err.str()));
+ }
+
+ // Setup DHCPv4-over-DHCPv6 IPC
+ try {
+ Dhcp4to6Ipc::instance().open();
+ } catch (const std::exception& ex) {
+ err << "error starting DHCPv4-over-DHCPv6 IPC "
+ " after server reconfiguration: " << ex.what();
+ return (isc::config::createAnswer(1, err.str()));
+ }
+
+ // Configure DHCP packet queueing
+ try {
+ data::ConstElementPtr qc;
+ qc = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl();
+ if (IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, qc)) {
+ LOG_INFO(dhcp4_logger, DHCP4_CONFIG_PACKET_QUEUE)
+ .arg(IfaceMgr::instance().getPacketQueue4()->getInfoStr());
+ }
+
+ } catch (const std::exception& ex) {
+ err << "Error setting packet queue controls after server reconfiguration: "
+ << ex.what();
+ return (isc::config::createAnswer(1, err.str()));
+ }
+
+ // Configure a callback to shut down the server when the bind socket
+ // attempts exceeded.
+ CfgIface::open_sockets_failed_callback_ =
+ std::bind(&ControlledDhcpv4Srv::openSocketsFailedCallback, srv, ph::_1);
+
+ // Configuration may change active interfaces. Therefore, we have to reopen
+ // sockets according to new configuration. It is possible that this
+ // operation will fail for some interfaces but the openSockets function
+ // guards against exceptions and invokes a callback function to
+ // log warnings. Since we allow that this fails for some interfaces there
+ // is no need to rollback configuration if socket fails to open on any
+ // of the interfaces.
+ CfgMgr::instance().getStagingCfg()->getCfgIface()->
+ openSockets(AF_INET, srv->getServerPort(),
+ getInstance()->useBroadcast());
+
+ // Install the timers for handling leases reclamation.
+ try {
+ CfgMgr::instance().getStagingCfg()->getCfgExpiration()->
+ setupTimers(&ControlledDhcpv4Srv::reclaimExpiredLeases,
+ &ControlledDhcpv4Srv::deleteExpiredReclaimedLeases,
+ server_);
+
+ } catch (const std::exception& ex) {
+ err << "unable to setup timers for periodically running the"
+ " reclamation of the expired leases: "
+ << ex.what() << ".";
+ return (isc::config::createAnswer(1, err.str()));
+ }
+
+ // Setup config backend polling, if configured for it.
+ auto ctl_info = CfgMgr::instance().getStagingCfg()->getConfigControlInfo();
+ if (ctl_info) {
+ long fetch_time = static_cast<long>(ctl_info->getConfigFetchWaitTime());
+ // Only schedule the CB fetch timer if the fetch wait time is greater
+ // than 0.
+ if (fetch_time > 0) {
+ // When we run unit tests, we want to use milliseconds unit for the
+ // specified interval. Otherwise, we use seconds. Note that using
+ // milliseconds as a unit in unit tests prevents us from waiting 1
+ // second on more before the timer goes off. Instead, we wait one
+ // millisecond which significantly reduces the test time.
+ if (!server_->inTestMode()) {
+ fetch_time = 1000 * fetch_time;
+ }
+
+ boost::shared_ptr<unsigned> failure_count(new unsigned(0));
+ TimerMgr::instance()->
+ registerTimer("Dhcp4CBFetchTimer",
+ std::bind(&ControlledDhcpv4Srv::cbFetchUpdates,
+ server_, CfgMgr::instance().getStagingCfg(),
+ failure_count),
+ fetch_time,
+ asiolink::IntervalTimer::ONE_SHOT);
+ TimerMgr::instance()->setup("Dhcp4CBFetchTimer");
+ }
+ }
+
+ // Finally, we can commit runtime option definitions in libdhcp++. This is
+ // exception free.
+ LibDHCP::commitRuntimeOptionDefs();
+
+ // This hook point notifies hooks libraries that the configuration of the
+ // DHCPv4 server has completed. It provides the hook library with the pointer
+ // to the common IO service object, new server configuration in the JSON
+ // format and with the pointer to the configuration storage where the
+ // parsed configuration is stored.
+ if (HooksManager::calloutsPresent(Hooks.hooks_index_dhcp4_srv_configured_)) {
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ callout_handle->setArgument("io_context", srv->getIOService());
+ callout_handle->setArgument("network_state", srv->getNetworkState());
+ callout_handle->setArgument("json_config", config);
+ callout_handle->setArgument("server_config", CfgMgr::instance().getStagingCfg());
+
+ HooksManager::callCallouts(Hooks.hooks_index_dhcp4_srv_configured_,
+ *callout_handle);
+
+ // If next step is DROP, report a configuration error.
+ if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
+ string error;
+ try {
+ callout_handle->getArgument("error", error);
+ } catch (NoSuchArgument const& ex) {
+ error = "unknown error";
+ }
+ return (isc::config::createAnswer(CONTROL_RESULT_ERROR, error));
+ }
+ }
+
+ // Apply multi threading settings.
+ // @note These settings are applied/updated only if no errors occur while
+ // applying the new configuration.
+ // @todo This should be fixed.
+ try {
+ CfgMultiThreading::apply(CfgMgr::instance().getStagingCfg()->getDHCPMultiThreading());
+ } catch (const std::exception& ex) {
+ err << "Error applying multi threading settings: "
+ << ex.what();
+ return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str()));
+ }
+
+ return (answer);
+}
+
+isc::data::ConstElementPtr
+ControlledDhcpv4Srv::checkConfig(isc::data::ConstElementPtr config) {
+
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_RECEIVED)
+ .arg(redactConfig(config)->str());
+
+ ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
+
+ // Single stream instance used in all error clauses
+ std::ostringstream err;
+
+ if (!srv) {
+ err << "Server object not initialized, can't process config.";
+ return (isc::config::createAnswer(1, err.str()));
+ }
+
+ return (configureDhcp4Server(*srv, config, true));
+}
+
+ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t server_port /*= DHCP4_SERVER_PORT*/,
+ uint16_t client_port /*= 0*/)
+ : Dhcpv4Srv(server_port, client_port), timer_mgr_(TimerMgr::instance()) {
+ if (getInstance()) {
+ isc_throw(InvalidOperation,
+ "There is another Dhcpv4Srv instance already.");
+ }
+ server_ = this; // remember this instance for later use in handlers
+
+ // TimerMgr uses IO service to run asynchronous timers.
+ TimerMgr::instance()->setIOService(getIOService());
+
+ // CommandMgr uses IO service to run asynchronous socket operations.
+ CommandMgr::instance().setIOService(getIOService());
+
+ // LeaseMgr uses IO service to run asynchronous timers.
+ LeaseMgr::setIOService(getIOService());
+
+ // HostMgr uses IO service to run asynchronous timers.
+ HostMgr::setIOService(getIOService());
+
+ // These are the commands always supported by the DHCPv4 server.
+ // Please keep the list in alphabetic order.
+ CommandMgr::instance().registerCommand("build-report",
+ std::bind(&ControlledDhcpv4Srv::commandBuildReportHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("config-backend-pull",
+ std::bind(&ControlledDhcpv4Srv::commandConfigBackendPullHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("config-get",
+ std::bind(&ControlledDhcpv4Srv::commandConfigGetHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("config-reload",
+ std::bind(&ControlledDhcpv4Srv::commandConfigReloadHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("config-set",
+ std::bind(&ControlledDhcpv4Srv::commandConfigSetHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("config-test",
+ std::bind(&ControlledDhcpv4Srv::commandConfigTestHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("config-write",
+ std::bind(&ControlledDhcpv4Srv::commandConfigWriteHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("dhcp-enable",
+ std::bind(&ControlledDhcpv4Srv::commandDhcpEnableHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("dhcp-disable",
+ std::bind(&ControlledDhcpv4Srv::commandDhcpDisableHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("libreload",
+ std::bind(&ControlledDhcpv4Srv::commandLibReloadHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("leases-reclaim",
+ std::bind(&ControlledDhcpv4Srv::commandLeasesReclaimHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("server-tag-get",
+ std::bind(&ControlledDhcpv4Srv::commandServerTagGetHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("shutdown",
+ std::bind(&ControlledDhcpv4Srv::commandShutdownHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("status-get",
+ std::bind(&ControlledDhcpv4Srv::commandStatusGetHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("version-get",
+ std::bind(&ControlledDhcpv4Srv::commandVersionGetHandler, this, ph::_1, ph::_2));
+
+ // Register statistic related commands
+ CommandMgr::instance().registerCommand("statistic-get",
+ std::bind(&StatsMgr::statisticGetHandler, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("statistic-reset",
+ std::bind(&StatsMgr::statisticResetHandler, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("statistic-remove",
+ std::bind(&StatsMgr::statisticRemoveHandler, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("statistic-get-all",
+ std::bind(&StatsMgr::statisticGetAllHandler, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("statistic-reset-all",
+ std::bind(&StatsMgr::statisticResetAllHandler, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("statistic-remove-all",
+ std::bind(&StatsMgr::statisticRemoveAllHandler, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("statistic-sample-age-set",
+ std::bind(&StatsMgr::statisticSetMaxSampleAgeHandler, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("statistic-sample-age-set-all",
+ std::bind(&ControlledDhcpv4Srv::commandStatisticSetMaxSampleAgeAllHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("statistic-sample-count-set",
+ std::bind(&StatsMgr::statisticSetMaxSampleCountHandler, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("statistic-sample-count-set-all",
+ std::bind(&ControlledDhcpv4Srv::commandStatisticSetMaxSampleCountAllHandler, this, ph::_1, ph::_2));
+}
+
+void ControlledDhcpv4Srv::shutdownServer(int exit_value) {
+ setExitValue(exit_value);
+ getIOService()->stop(); // Stop ASIO transmissions
+ shutdown(); // Initiate DHCPv4 shutdown procedure.
+}
+
+ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
+ try {
+ LeaseMgrFactory::destroy();
+ HostMgr::create();
+ cleanup();
+
+ // The closure captures either a shared pointer (memory leak)
+ // or a raw pointer (pointing to a deleted object).
+ DatabaseConnection::db_lost_callback_ = 0;
+ DatabaseConnection::db_recovered_callback_ = 0;
+ DatabaseConnection::db_failed_callback_ = 0;
+
+ timer_mgr_->unregisterTimers();
+
+ // Close the command socket (if it exists).
+ CommandMgr::instance().closeCommandSocket();
+
+ // Deregister any registered commands (please keep in alphabetic order)
+ CommandMgr::instance().deregisterCommand("build-report");
+ CommandMgr::instance().deregisterCommand("config-backend-pull");
+ CommandMgr::instance().deregisterCommand("config-get");
+ CommandMgr::instance().deregisterCommand("config-reload");
+ CommandMgr::instance().deregisterCommand("config-set");
+ CommandMgr::instance().deregisterCommand("config-test");
+ CommandMgr::instance().deregisterCommand("config-write");
+ CommandMgr::instance().deregisterCommand("dhcp-disable");
+ CommandMgr::instance().deregisterCommand("dhcp-enable");
+ CommandMgr::instance().deregisterCommand("leases-reclaim");
+ CommandMgr::instance().deregisterCommand("libreload");
+ CommandMgr::instance().deregisterCommand("server-tag-get");
+ CommandMgr::instance().deregisterCommand("shutdown");
+ CommandMgr::instance().deregisterCommand("statistic-get");
+ CommandMgr::instance().deregisterCommand("statistic-get-all");
+ CommandMgr::instance().deregisterCommand("statistic-remove");
+ CommandMgr::instance().deregisterCommand("statistic-remove-all");
+ CommandMgr::instance().deregisterCommand("statistic-reset");
+ CommandMgr::instance().deregisterCommand("statistic-reset-all");
+ CommandMgr::instance().deregisterCommand("statistic-sample-age-set");
+ CommandMgr::instance().deregisterCommand("statistic-sample-age-set-all");
+ CommandMgr::instance().deregisterCommand("statistic-sample-count-set");
+ CommandMgr::instance().deregisterCommand("statistic-sample-count-set-all");
+ CommandMgr::instance().deregisterCommand("status-get");
+ CommandMgr::instance().deregisterCommand("version-get");
+
+ // LeaseMgr uses IO service to run asynchronous timers.
+ LeaseMgr::setIOService(IOServicePtr());
+
+ // HostMgr uses IO service to run asynchronous timers.
+ HostMgr::setIOService(IOServicePtr());
+ } catch (...) {
+ // Don't want to throw exceptions from the destructor. The server
+ // is shutting down anyway.
+ ;
+ }
+
+ server_ = NULL; // forget this instance. There should be no callback anymore
+ // at this stage anyway.
+}
+
+void
+ControlledDhcpv4Srv::reclaimExpiredLeases(const size_t max_leases,
+ const uint16_t timeout,
+ const bool remove_lease,
+ const uint16_t max_unwarned_cycles) {
+ try {
+ server_->alloc_engine_->reclaimExpiredLeases4(max_leases, timeout,
+ remove_lease,
+ max_unwarned_cycles);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcp4_logger, DHCP4_RECLAIM_EXPIRED_LEASES_FAIL)
+ .arg(ex.what());
+ }
+ // We're using the ONE_SHOT timer so there is a need to re-schedule it.
+ TimerMgr::instance()->setup(CfgExpiration::RECLAIM_EXPIRED_TIMER_NAME);
+}
+
+void
+ControlledDhcpv4Srv::deleteExpiredReclaimedLeases(const uint32_t secs) {
+ server_->alloc_engine_->deleteExpiredReclaimedLeases4(secs);
+ // We're using the ONE_SHOT timer so there is a need to re-schedule it.
+ TimerMgr::instance()->setup(CfgExpiration::FLUSH_RECLAIMED_TIMER_NAME);
+}
+
+bool
+ControlledDhcpv4Srv::dbLostCallback(ReconnectCtlPtr db_reconnect_ctl) {
+ if (!db_reconnect_ctl) {
+ // This should never happen
+ LOG_ERROR(dhcp4_logger, DHCP4_DB_RECONNECT_NO_DB_CTL);
+ return (false);
+ }
+
+ // Disable service until the connection is recovered.
+ if (db_reconnect_ctl->retriesLeft() == db_reconnect_ctl->maxRetries() &&
+ db_reconnect_ctl->alterServiceState()) {
+ network_state_->disableService(NetworkState::Origin::DB_CONNECTION);
+ }
+
+ LOG_INFO(dhcp4_logger, DHCP4_DB_RECONNECT_LOST_CONNECTION);
+
+ // If reconnect isn't enabled log it, initiate a shutdown if needed and
+ // return false.
+ if (!db_reconnect_ctl->retriesLeft() ||
+ !db_reconnect_ctl->retryInterval()) {
+ LOG_INFO(dhcp4_logger, DHCP4_DB_RECONNECT_DISABLED)
+ .arg(db_reconnect_ctl->retriesLeft())
+ .arg(db_reconnect_ctl->retryInterval());
+ if (db_reconnect_ctl->exitOnFailure()) {
+ shutdownServer(EXIT_FAILURE);
+ }
+ return (false);
+ }
+
+ return (true);
+}
+
+bool
+ControlledDhcpv4Srv::dbRecoveredCallback(ReconnectCtlPtr db_reconnect_ctl) {
+ if (!db_reconnect_ctl) {
+ // This should never happen
+ LOG_ERROR(dhcp4_logger, DHCP4_DB_RECONNECT_NO_DB_CTL);
+ return (false);
+ }
+
+ // Enable service after the connection is recovered.
+ if (db_reconnect_ctl->alterServiceState()) {
+ network_state_->enableService(NetworkState::Origin::DB_CONNECTION);
+ }
+
+ LOG_INFO(dhcp4_logger, DHCP4_DB_RECONNECT_SUCCEEDED);
+
+ db_reconnect_ctl->resetRetries();
+
+ return (true);
+}
+
+bool
+ControlledDhcpv4Srv::dbFailedCallback(ReconnectCtlPtr db_reconnect_ctl) {
+ if (!db_reconnect_ctl) {
+ // This should never happen
+ LOG_ERROR(dhcp4_logger, DHCP4_DB_RECONNECT_NO_DB_CTL);
+ return (false);
+ }
+
+ LOG_INFO(dhcp4_logger, DHCP4_DB_RECONNECT_FAILED)
+ .arg(db_reconnect_ctl->maxRetries());
+
+ if (db_reconnect_ctl->exitOnFailure()) {
+ shutdownServer(EXIT_FAILURE);
+ }
+
+ return (true);
+}
+
+void
+ControlledDhcpv4Srv::openSocketsFailedCallback(ReconnectCtlPtr reconnect_ctl) {
+ if (!reconnect_ctl) {
+ // This should never happen
+ LOG_ERROR(dhcp4_logger, DHCP4_OPEN_SOCKETS_NO_RECONNECT_CTL);
+ return;
+ }
+
+ LOG_INFO(dhcp4_logger, DHCP4_OPEN_SOCKETS_FAILED)
+ .arg(reconnect_ctl->maxRetries());
+
+ if (reconnect_ctl->exitOnFailure()) {
+ shutdownServer(EXIT_FAILURE);
+ }
+}
+
+void
+ControlledDhcpv4Srv::cbFetchUpdates(const SrvConfigPtr& srv_cfg,
+ boost::shared_ptr<unsigned> failure_count) {
+ // stop thread pool (if running)
+ MultiThreadingCriticalSection cs;
+
+ try {
+ // Fetch any configuration backend updates since our last fetch.
+ server_->getCBControl()->databaseConfigFetch(srv_cfg,
+ CBControlDHCPv4::FetchMode::FETCH_UPDATE);
+ (*failure_count) = 0;
+
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcp4_logger, DHCP4_CB_PERIODIC_FETCH_UPDATES_FAIL)
+ .arg(ex.what());
+
+ // We allow at most 10 consecutive failures after which we stop
+ // making further attempts to fetch the configuration updates.
+ // Let's return without re-scheduling the timer.
+ if (++(*failure_count) > 10) {
+ LOG_ERROR(dhcp4_logger,
+ DHCP4_CB_PERIODIC_FETCH_UPDATES_RETRIES_EXHAUSTED);
+ return;
+ }
+ }
+
+ // Reschedule the timer to fetch new updates or re-try if
+ // the previous attempt resulted in an error.
+ if (TimerMgr::instance()->isTimerRegistered("Dhcp4CBFetchTimer")) {
+ TimerMgr::instance()->setup("Dhcp4CBFetchTimer");
+ }
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.h b/src/bin/dhcp4/ctrl_dhcp4_srv.h
new file mode 100644
index 0000000..2c34610
--- /dev/null
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.h
@@ -0,0 +1,473 @@
+// Copyright (C) 2012-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/.
+
+#ifndef CTRL_DHCPV4_SRV_H
+#define CTRL_DHCPV4_SRV_H
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/asiolink.h>
+#include <cc/data.h>
+#include <cc/command_interpreter.h>
+#include <util/reconnect_ctl.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <dhcp4/dhcp4_srv.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Controlled version of the DHCPv4 server
+///
+/// This is a class that is responsible for DHCPv4 server being controllable,
+/// by reading configuration file from disk.
+class ControlledDhcpv4Srv : public isc::dhcp::Dhcpv4Srv {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param server_port UDP port to be opened for DHCP traffic
+ /// @param client_port UDP port where all responses are sent to.
+ ControlledDhcpv4Srv(uint16_t server_port = DHCP4_SERVER_PORT,
+ uint16_t client_port = 0);
+
+ /// @brief Destructor.
+ virtual ~ControlledDhcpv4Srv();
+
+ /// @brief Initializes the server.
+ ///
+ /// It reads the JSON file from disk or may perform any other setup
+ /// operation. In particular, it also install signal handlers.
+ ///
+ /// This method may throw if initialization fails.
+ void init(const std::string& config_file);
+
+ /// @brief Configure DHCPv4 server using the configuration file specified.
+ ///
+ /// This utility method is called whenever we know a filename of the config
+ /// and need to load it. It calls config-set command once the content of
+ /// the file has been loaded and verified to be a sane JSON configuration.
+ /// config-set handler will process the config file (load it as current
+ /// configuration).
+ ///
+ /// This function is used to both configure the DHCP server on its startup
+ /// and dynamically reconfigure the server when SIGHUP signal is received.
+ ///
+ /// It fetches DHCPv4 server's configuration from the 'Dhcp4' section of
+ /// the JSON configuration file.
+ ///
+ /// @param file_name name of the file to be loaded
+ /// @return status of the file loading and outcome of config-set
+ isc::data::ConstElementPtr
+ loadConfigFile(const std::string& file_name);
+
+ /// @brief Performs cleanup, immediately before termination
+ ///
+ /// This method performs final clean up, just before the Dhcpv4Srv object
+ /// is destroyed. Currently it is a no-op.
+ void cleanup();
+
+ /// @brief Initiates shutdown procedure for the whole DHCPv4 server.
+ /// @param exit_value integer value to the process should exit with.
+ void shutdownServer(int exit_value);
+
+ /// @brief Command processor
+ ///
+ /// This method is uniform for all config backends. It processes received
+ /// command (as a string + JSON arguments). Internally, it's just a
+ /// wrapper that calls process*Command() methods and catches exceptions
+ /// in them.
+ ///
+ /// Currently supported commands are:
+ /// - config-reload
+ /// - config-test
+ /// - shutdown
+ /// - libreload
+ /// - leases-reclaim
+ /// ...
+ ///
+ /// @note It never throws.
+ ///
+ /// @param command Text representation of the command (e.g. "shutdown")
+ /// @param args Optional parameters
+ ///
+ /// @return status of the command
+ static isc::data::ConstElementPtr
+ processCommand(const std::string& command, isc::data::ConstElementPtr args);
+
+ /// @brief Configuration processor
+ ///
+ /// This is a method for handling incoming configuration updates.
+ /// This method should be called by all configuration backends when the
+ /// server is starting up or when configuration has changed.
+ ///
+ /// As pointer to this method is used a callback in ASIO used in
+ /// ModuleCCSession, it has to be static.
+ ///
+ /// @param new_config textual representation of the new configuration
+ ///
+ /// @return status of the config update
+ static isc::data::ConstElementPtr
+ processConfig(isc::data::ConstElementPtr new_config);
+
+ /// @brief Configuration checker
+ ///
+ /// This is a method for checking incoming configuration.
+ ///
+ /// @param new_config JSON representation of the new configuration
+ ///
+ /// @return status of the config check
+ isc::data::ConstElementPtr
+ checkConfig(isc::data::ConstElementPtr new_config);
+
+ /// @brief Returns pointer to the sole instance of Dhcpv4Srv
+ ///
+ /// @return server instance (may return NULL, if called before server is spawned)
+ static ControlledDhcpv4Srv* getInstance() {
+ return (server_);
+ }
+
+private:
+
+ /// @brief Callback that will be called from iface_mgr when data
+ /// is received over control socket.
+ ///
+ /// This static callback method is called from IfaceMgr::receive4() method,
+ /// when there is a new command or configuration sent over control socket
+ /// (that was sent from some yet unspecified sender).
+ static void sessionReader(void);
+
+ /// @brief Handler for processing 'shutdown' command
+ ///
+ /// This handler processes shutdown command, which initializes shutdown
+ /// procedure.
+ /// @param command (parameter ignored)
+ /// @param args (parameter ignored)
+ ///
+ /// @return status of the command
+ isc::data::ConstElementPtr
+ commandShutdownHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief Handler for processing 'libreload' command
+ ///
+ /// This handler processes libreload command, which unloads all hook
+ /// libraries and reloads them.
+ ///
+ /// @param command (parameter ignored)
+ /// @param args (parameter ignored)
+ ///
+ /// @return status of the command
+ isc::data::ConstElementPtr
+ commandLibReloadHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief Handler for processing 'config-reload' command
+ ///
+ /// This handler processes config-reload command, which processes
+ /// configuration specified in args parameter.
+ ///
+ /// @param command (parameter ignored)
+ /// @param args configuration to be processed
+ ///
+ /// @return status of the command
+ isc::data::ConstElementPtr
+ commandConfigReloadHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for processing 'get-config' command
+ ///
+ /// This handler processes get-config command, which retrieves
+ /// the current configuration and returns it in response.
+ ///
+ /// @param command (ignored)
+ /// @param args (ignored)
+ /// @return current configuration wrapped in a response
+ isc::data::ConstElementPtr
+ commandConfigGetHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for processing 'write-config' command
+ ///
+ /// This handle processes write-config command, which writes the
+ /// current configuration to disk. This command takes one optional
+ /// parameter called filename. If specified, the current configuration
+ /// will be written to that file. If not specified, the file used during
+ /// Kea start-up will be used. To avoid any exploits, the path is
+ /// always relative and .. is not allowed in the filename. This is
+ /// a security measure against exploiting file writes remotely.
+ ///
+ /// @param command (ignored)
+ /// @param args may contain optional string argument filename
+ /// @return status of the configuration file write
+ isc::data::ConstElementPtr
+ commandConfigWriteHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for processing 'config-set' command
+ ///
+ /// This handler processes config-set command, which processes
+ /// configuration specified in args parameter.
+ /// @param command (parameter ignored)
+ /// @param args configuration to be processed. Expected format:
+ /// map containing Dhcp4 map that contains DHCPv4 server configuration.
+ ///
+ /// @return status of the command
+ isc::data::ConstElementPtr
+ commandConfigSetHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for processing 'config-test' command
+ ///
+ /// This handler processes config-test command, which checks
+ /// configuration specified in args parameter.
+ /// @param command (parameter ignored)
+ /// @param args configuration to be checked. Expected format:
+ /// map containing Dhcp4 map that contains DHCPv4 server configuration.
+ ///
+ /// @return status of the command
+ isc::data::ConstElementPtr
+ commandConfigTestHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief A handler for processing 'dhcp-disable' command.
+ ///
+ /// @param command command name (ignored).
+ /// @param args arguments for the command. It must be a map and
+ /// it may include optional 'max-period' parameter.
+ ///
+ /// @return result of the command.
+ isc::data::ConstElementPtr
+ commandDhcpDisableHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief A handler for processing 'dhcp-enable' command.
+ ///
+ /// @param command command name (ignored)
+ /// @param args arguments for the command (ignored).
+ ///
+ /// @return result of the command.
+ isc::data::ConstElementPtr
+ commandDhcpEnableHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @Brief handler for processing 'version-get' command
+ ///
+ /// This handler processes version-get command, which returns
+ /// over the control channel the -v and -V command line arguments.
+ /// @param command (parameter ignored)
+ /// @param args (parameter ignored)
+ ///
+ /// @return status of the command with the version in text and
+ /// the extended version in arguments.
+ isc::data::ConstElementPtr
+ commandVersionGetHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for processing 'build-report' command
+ ///
+ /// This handler processes build-report command, which returns
+ /// over the control channel the -W command line argument.
+ /// @param command (parameter ignored)
+ /// @param args (parameter ignored)
+ ///
+ /// @return status of the command with the config report
+ isc::data::ConstElementPtr
+ commandBuildReportHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief Handler for processing 'leases-reclaim' command
+ ///
+ /// This handler processes leases-reclaim command, which triggers
+ /// the leases reclamation immediately.
+ /// No limit for processing time or number of processed leases applies.
+ ///
+ /// @param command (parameter ignored)
+ /// @param args arguments map { "remove": <bool> }
+ /// if true a lease is removed when it is reclaimed,
+ /// if false its state is changed to "expired-reclaimed".
+ ///
+ /// @return status of the command (should be success unless args
+ /// was not a Bool Element).
+ isc::data::ConstElementPtr
+ commandLeasesReclaimHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for server-tag-get command
+ ///
+ /// This method handles the server-tag-get command, which retrieves
+ /// the current server tag and returns it in response.
+ ///
+ /// @param command (ignored)
+ /// @param args (ignored)
+ /// @return current configuration wrapped in a response
+ isc::data::ConstElementPtr
+ commandServerTagGetHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for config-backend-pull command
+ ///
+ /// This method handles the config-backend-pull command, which updates
+ /// the server configuration from the Config Backends immediately.
+ ///
+ /// @param command (parameter ignored)
+ /// @param args (ignored)
+ ///
+ /// @return status of the command/
+ isc::data::ConstElementPtr
+ commandConfigBackendPullHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for processing 'status-get' command
+ ///
+ /// This handler processes status-get command, which retrieves
+ /// the server process information i.e. the pid and returns it in response.
+ ///
+ /// @param command (ignored)
+ /// @param args (ignored)
+ /// @return process information wrapped in a response
+ isc::data::ConstElementPtr
+ commandStatusGetHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for processing 'statistic-sample-count-set-all' command
+ ///
+ /// This handler processes statistic-sample-count-set-all command,
+ /// which sets max_sample_count_ limit of all statistics and the default.
+ /// @ref isc::stats::StatsMgr::statisticSetMaxSampleCountAllHandler
+ ///
+ /// @param command (ignored)
+ /// @param args structure containing a map that contains "max-samples"
+ /// @return process information wrapped in a response
+ isc::data::ConstElementPtr
+ commandStatisticSetMaxSampleCountAllHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for processing 'statistic-sample-age-set-all' command
+ ///
+ /// This handler processes statistic-sample-age-set-all command,
+ /// which sets max_sample_age_ limit of all statistics and the default.
+ /// @ref isc::stats::StatsMgr::statisticSetMaxSampleAgeAllHandler
+ ///
+ /// @param command (ignored)
+ /// @param args structure containing a map that contains "duration"
+ /// @return process information wrapped in a response
+ isc::data::ConstElementPtr
+ commandStatisticSetMaxSampleAgeAllHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief Reclaims expired IPv4 leases and reschedules timer.
+ ///
+ /// This is a wrapper method for @c AllocEngine::reclaimExpiredLeases4.
+ /// It reschedules the timer for leases reclamation upon completion of
+ /// this method.
+ ///
+ /// @param max_leases Maximum number of leases to be reclaimed.
+ /// @param timeout Maximum amount of time that the reclamation routine
+ /// may be processing expired leases, expressed in milliseconds.
+ /// @param remove_lease A boolean value indicating if the lease should
+ /// be removed when it is reclaimed (if true) or it should be left in the
+ /// database in the "expired-reclaimed" state (if false).
+ /// @param max_unwarned_cycles A number of consecutive processing cycles
+ /// of expired leases, after which the system issues a warning if there
+ /// are still expired leases in the database. If this value is 0, the
+ /// warning is never issued.
+ void reclaimExpiredLeases(const size_t max_leases, const uint16_t timeout,
+ const bool remove_lease,
+ const uint16_t max_unwarned_cycles);
+
+ /// @brief Deletes reclaimed leases and reschedules the timer.
+ ///
+ /// This is a wrapper method for @c AllocEngine::deleteExpiredReclaimed4.
+ /// It reschedules the timer for leases reclamation upon completion of
+ /// this method.
+ ///
+ /// @param secs Minimum number of seconds after which a lease can be
+ /// deleted.
+ void deleteExpiredReclaimedLeases(const uint32_t secs);
+
+ /// @brief Callback DB backends should be invoked upon loss of the
+ /// connectivity.
+ ///
+ /// This function is invoked by DB backends when they detect a loss of
+ /// connectivity. The parameter, db_reconnect_ctl, conveys the configured
+ /// maximum number of reconnect retries as well as the interval to wait
+ /// between retry attempts.
+ ///
+ /// If either value is zero, reconnect is presumed to be disabled and
+ /// the function will schedule a shutdown and return false. This instructs
+ /// the DB backend layer (the caller) to treat the connectivity loss as
+ /// fatal. It stops the DHCP service until the connection is recovered.
+ ///
+ /// @param db_reconnect_ctl pointer to the ReconnectCtl containing the
+ /// configured reconnect parameters
+ ///
+ /// @return false if reconnect is not configured, true otherwise
+ bool dbLostCallback(util::ReconnectCtlPtr db_reconnect_ctl);
+
+ /// @brief Callback DB backends should be invoked upon restoration of
+ /// connectivity.
+ ///
+ /// This function is invoked by DB backends when they recover the
+ /// connectivity. It starts the DHCP service after the connection is
+ /// recovered.
+ ///
+ /// @param db_reconnect_ctl pointer to the ReconnectCtl containing the
+ /// configured reconnect parameters
+ ///
+ /// @return false if reconnect is not configured, true otherwise
+ bool dbRecoveredCallback(util::ReconnectCtlPtr db_reconnect_ctl);
+
+ /// @brief Callback DB backends should be invoked upon failing to restore
+ /// connectivity.
+ ///
+ /// This function is invoked by DB backends when they fail to recover the
+ /// connectivity. It stops the server.
+ ///
+ /// @param db_reconnect_ctl pointer to the ReconnectCtl containing the
+ /// configured reconnect parameters
+ ///
+ /// @return false if reconnect is not configured, true otherwise
+ bool dbFailedCallback(util::ReconnectCtlPtr db_reconnect_ctl);
+
+ /// @brief This callback should be invoked upon failing to bind sockets.
+ ///
+ /// This function is invoked during the configuration of the interfaces
+ /// when they fail to bind the service sockets. It may stop the server.
+ ///
+ /// @param reconnect_ctl pointer to the ReconnectCtl containing the
+ /// configured reconnect parameters
+ void openSocketsFailedCallback(util::ReconnectCtlPtr reconnect_ctl);
+
+ /// @brief Callback invoked periodically to fetch configuration updates
+ /// from the Config Backends.
+ ///
+ /// This method calls @c CBControlDHCPv4::databaseConfigFetch and then
+ /// reschedules the timer.
+ ///
+ /// @param srv_cfg Server configuration holding the database credentials
+ /// and server tag.
+ /// @param failure_count pointer to failure counter which causes this
+ /// callback to stop scheduling the timer after 10 consecutive failures
+ /// to fetch the updates.
+ void cbFetchUpdates(const SrvConfigPtr& srv_cfg,
+ boost::shared_ptr<unsigned> failure_count);
+
+ /// @brief Static pointer to the sole instance of the DHCP server.
+ ///
+ /// This is required for config and command handlers to gain access to
+ /// the server. Some of them need to be static methods.
+ static ControlledDhcpv4Srv* server_;
+
+ /// @brief Instance of the @c TimerMgr.
+ ///
+ /// Shared pointer to the instance of timer @c TimerMgr is held here to
+ /// make sure that the @c TimerMgr outlives instance of this class.
+ TimerMgrPtr timer_mgr_;
+};
+
+} // namespace dhcp
+} // namespace isc
+
+#endif
diff --git a/src/bin/dhcp4/dhcp4.dox b/src/bin/dhcp4/dhcp4.dox
new file mode 100644
index 0000000..e6034b5
--- /dev/null
+++ b/src/bin/dhcp4/dhcp4.dox
@@ -0,0 +1,352 @@
+// Copyright (C) 2012-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/.
+
+/**
+ @page dhcp4 DHCPv4 Server Component
+
+Kea includes the "kea-dhcp4" component, which is the DHCPv4 server
+implementation. This component is built around the
+@ref isc::dhcp::Dhcpv4Srv class which controls all major operations
+performed by the server such as: DHCP messages processing, callouts
+execution for many hook points, FQDN processing and interactions with the
+"kea-dhcp-ddns" component, lease allocation, system signals handling etc.
+
+The "kea-dhcp4" component requires linking with many different libraries
+to obtain access to common functions like: interfaces and sockets
+management, configuration parsing, leases management and allocation,
+hooks infrastructure, statistics management etc.
+
+The following sections walk through some of the details of the "kea-dhcp4"
+component implementation.
+
+@section dhcpv4ConfigParser Configuration Parser in DHCPv4
+
+Note: parsers are currently being migrated to @ref isc::data::SimpleParser. See
+@ref ccSimpleParser page for details.
+
+The common configuration parsers for the DHCP servers are located in the
+src/lib/dhcpsrv/parsers/ directory. Parsers specific to the DHCPv4 component
+are located in the src/bin/dhcp4/json_config_parser.cc. These parsers derive
+from the common configuration parsers and customize their behavior. For
+example: the @c Subnet4ConfigParser is used to parse parameters
+describing a single subnet. It derives from the @c
+isc::dhcp::SubnetConfigParser, which implements the common base for both
+DHCPv4 and DHCPv6 subnets. The @ref Subnet4ConfigParser
+implements the @c initSubnet abstract method, which creates an instance of
+the DHCPv4 subnet. This method is invoked by the parent class.
+
+Some parsers for the DHCPv4 server derive from the isc::dhcp::DhcpConfigParser
+class directly. This is an abstract class, defining a basic interface for
+all configuration parsers. All DHCPv4 parsers deriving from this class
+directly have their entire implementation in the
+src/bin/dhcp4/json_config_parser.cc.
+
+@section dhcpv4ConfigParserBison Configuration Parser for DHCPv4 (bison)
+
+If you are here only to learn absolute minimum about the new parser, here's how you
+use it:
+
+@code
+ // The following code:
+ json = isc::data::Element::fromJSONFile(file_name, true);
+
+ // can be replaced with this:
+ Parser4Context parser;
+ json = parser.parseFile(file_name, Parser4Context::PARSER_DHCP4);
+@endcode
+
+For an introduction, rationale and issues the new parser tries to address,
+see @ref dhcpv6ConfigParserBison.
+
+The code change for 5017 introduces flex/bison based parser. It is
+essentially defined in two files: dhcp4_lexer.ll, which defines
+regular expressions that are used on the input (be it a file or a
+string in memory). In essence, this code is being called repeatedly
+and each time it returns a token. This repeats until either the
+parsing is complete or syntax error is encountered. For detailed
+discussion, how they operate see @ref dhcpv6ConfigParserBison.
+
+@section dhcpv4ConfigSubParser Parsing Partial Configuration in DHCPv4
+
+See @ref dhcpv6ConfigSubParser.
+
+@section dhcp4ParserIncludes Config File Includes
+
+See @ref dhcp6ParserIncludes.
+
+@section dhcp4ParserConflicts Avoiding syntactical conflicts in parsers
+
+See @ref dhcp6ParserConflicts.
+
+@section dhcpv4ConfigInherit DHCPv4 configuration inheritance
+
+One notable useful feature of DHCP configuration is its parameter inheritance.
+For example, the "renew-timer" value may be specified at a global scope and it then
+applies to all subnets. However, some subnets may have it overwritten with subnet
+specific values that takes precedence over global values that are considered
+defaults. The parameters inheritance is implemented by means of the "global
+context". The global context is represented by the isc::dhcp::ParserContext
+class and it holds pointers to storage of different kinds, e.g. text parameters,
+numeric parameters etc. When the server is parsing the top level configuration
+parameters it passes pointers to the storages of the appropriate kind, to the
+parsers being invoked to parse the global values. Parsers will store the
+parsed values into these storages. Once the global parameters are stored in the
+global context, the parsers for the nested configuration parameters are invoked.
+These parsers check the presence of the parameters overriding the values of
+the global parameters. If a value is not present, the values from the global
+context is used.
+
+A good example of inheritance is the implementation of the @ref
+isc::dhcp::SubnetConfigParser. The @c getParam method is used throughout the
+class to obtain values of the parameters defining a subnet. It first checks
+if the specific value is present in the local values storage. If it is not
+present, it uses the value from the global context.
+
+ @code
+ isc::dhcp::Triplet<uint32_t>
+ SubnetConfigParser::getParam(const std::string& name) {
+ uint32_t value = 0;
+ try {
+ // look for local value
+ value = uint32_values_->getParam(name);
+ } catch (const DhcpConfigError &) {
+ try {
+ // no local, use global value
+ value = global_context_->uint32_values_->getParam(name);
+ } catch (const DhcpConfigError &) {
+ isc_throw(DhcpConfigError, "Mandatory parameter " << name
+ << " missing (no global default and no subnet-"
+ << "specific value)");
+ }
+ }
+
+ return (Triplet<uint32_t>(value));
+}
+@endcode
+
+Note that if the value is neither present in the local storage nor in the global
+context an error is signaled.
+
+Parameter inheritance is done once, during the reconfiguration phase.
+Reconfigurations are rare, so extra logic here is not a problem. On the other
+hand, values of those parameters may be used thousands times per second, so
+access to these parameters must be as efficient as possible. In fact,
+currently the code has to only call @c Subnet4::getT1(), regardless if the
+"renew-timer" has been specified as a global or subnet specific value.
+
+Debugging a configuration parser may be confusing. Therefore there is a special
+class called DebugParser. It does not configure anything, but just
+accepts any parameter of any type. If requested to commit configuration, it will
+print out received parameter name and its value. This class is not currently used,
+but it is convenient to have it every time a new parameter is added to the DHCP
+configuration. For that purpose it should be left in the code.
+
+@section dhcpv4OptionsParse Custom functions to parse message options
+
+The DHCPv4 server implementation provides a generic support to define option
+formats and set option values. A number of options formats have been defined
+for standard options in libdhcp++. However, the formats for vendor specific
+options are dynamically configured by the server's administrator and thus can't
+be stored in libdhcp++. Such option formats are stored in the
+@ref isc::dhcp::CfgMgr. The libdhcp++ provides functions for recursive parsing
+of options which may be encapsulated by other options up to any level of
+encapsulation, but these functions are unaware of the option formats defined
+in the @ref isc::dhcp::CfgMgr because they belong to a different library.
+Therefore, the generic functions @ref isc::dhcp::LibDHCP::unpackOptions4 and
+@ref isc::dhcp::LibDHCP::unpackOptions6 are only useful to parse standard
+options whose definitions are provided in the libdhcp++. In order to overcome
+this problem a callback mechanism has been implemented in @c Option and @c Pkt4
+classes. By installing a callback function on an instance of @c Pkt4, the
+server may provide a custom implementation of the options parsing algorithm.
+This callback function will take precedence over the @c LibDHCP::unpackOptions4
+and @c LibDHCP::unpackOptions6 functions. With this approach, the callback is
+implemented within the context of the server and it has access to all objects
+which define its configuration (including dynamically created option
+definitions).
+
+Private (codes 224-254) and VSI (code 43) options are not decoded
+by @c LibDHCP::unpackOptions4 but by @ref isc::dhcp::Dhcpv4Srv::deferredUnpack
+function after classification. To make this function to perform or not
+deferred processing the simplest is to add or not the option code
+to the @ref isc::dhcp::Pkt4::getDeferredOptions list.
+
+@section dhcpv4DDNSIntegration DHCPv4 Server Support for the Dynamic DNS Updates
+The DHCPv4 server supports processing of the DHCPv4 Client FQDN option (RFC4702)
+and the DHCPv4 Host Name option (RFC2132). A client may send one of these options
+to convey its fully qualified or partial name to the server. The server may use
+this name to perform DNS updates for the client. If server receives both options
+in the same message, the DHCPv4 Client FQDN %Option is processed and the Host
+Name option is ignored. If only Host Name Option is present in the client's
+message, it is used to update DNS.
+
+The server may be configured to use a different name to perform DNS update for the
+client. In this case the server will return one of the DHCPv4 Client FQDN or
+Host Name %Option in its response with the name which was selected for the
+client to indicate that this name will be used to perform DNS update.
+
+The kea-dhcp-ddns process is responsible for the actual communication with the
+DNS, i.e. to send DNS update messages. The kea-dhcp4 module is responsible for
+generating @ref isc::dhcp_ddns::NameChangeRequest and sending it to
+the kea-dhcp-ddns module. The @ref isc::dhcp_ddns::NameChangeRequest object
+represents changes to the DNS bindings, related to acquisition, renewal or
+release of the DHCP lease. The kea-dhcp4 module implements the simple FIFO queue
+of the NameChangeRequest objects. The module logic, which processes the incoming
+DHCPv4 Client FQDN and Host Name Options puts these requests into the FIFO queue.
+
+@todo Currently the FIFO queue is not processed after the NameChangeRequests are
+generated and added to it. In the future implementation steps it is planned to
+create a code which will check if there are any outstanding requests in the queue
+and send them to the kea-dhcp-ddns module when server is idle waiting for DHCP
+messages.
+
+When client gets an address from the server, a DHCPv4 server may generate 0, 1
+or 2 NameChangeRequests during single message processing. Server generates no
+NameChangeRequests if it is not configured to update DNS or it rejects the DNS
+update for any other reason.
+
+The server may generate one NameChangeRequest in the case where a client acquires a new
+lease or it releases an existing one. In the former case, the NameChangeRequest
+type is CHG_ADD, which indicates that the kea-dhcp-ddns module should add a new
+DNS binding for the client, and it is assumed that there is no DNS binding for
+this client already. In the latter case, the NameChangeRequest type is CHG_REMOVE
+to indicate to the kea-dhcp-ddns module that an existing DNS binding should be
+removed from the DNS. The binding consists of the forward and reverse mapping.
+The server may only remove the mapping which it had added. Therefore, the lease
+database holds the information which updates (no update, reverse only update,
+forward only update or both reverse and forward update) have been performed when
+the lease was acquired or renewed. Server checks this information to make a
+decision which mapping it is supposed to remove when lease is released.
+
+The server may generate two NameChangeRequests in the case where client is
+renewing a lease and it already has a DNS binding for that lease. The DHCPv4
+server will check if there is an existing lease for the client which has sent a
+message and if DNS Updates had been performed for this lease. If the notion of
+client's FQDN changes, comparing to the information stored in the lease
+database, the DHCPv4 has to remove an existing binding from the DNS and then add
+a new binding according to the new FQDN information received from the client. If
+the client's FQDN information (including the client's name and type of update
+performed) doesn't change comparing to the NameChangeRequest is not generated.
+
+The DHCPv4 Client FQDN %Option comprises flags which communicate to the server
+what updates (if any) client expects the server to perform. Server may be
+configured to obey client's preference or to do FQDN processing in a different way.
+If the server overrides client's preference it will communicate it by sending
+the DHCPv4 Client FQDN %Option in its responses to a client, with the appropriate
+flags set.
+
+@todo Note: the current implementation doesn't allow configuration of the
+server's behavior with respect to DNS Updates. This is planned for the future.
+The default behavior is constituted by the set of constants defined in the
+(upper part of) dhcp4_srv.cc file. Once the configuration is implemented,
+these constants will be removed.
+
+@section dhcpv4Classifier DHCPv4 Client Classification
+
+The Kea DHCPv4 currently supports two classification modes: simplified client
+classification (that was an early implementation that used values of vendor class option)
+and full client classification.
+
+@subsection dhcpv4ClassifierSimple Simple Client Classification in DHCPv4
+
+The Kea DHCPv4 server supports simplified client classification. It is called
+"simplified", because the incoming packets are classified based on the content
+of the vendor class (60) option. More flexible classification was added in 1.0
+and is described in @ref dhcpv4ClassifierFull .
+
+For each incoming packet, @ref isc::dhcp::Dhcpv4Srv::classifyPacket() method is called.
+It attempts to extract content of the vendor class option and interpret as a name
+of the class. For now, the code has been tested with two classes used in cable modem
+networks: eRouter1.0 and docsis3.0, but any other content of the vendor class option will
+be interpreted as a class name.
+
+In principle any given packet can belong to zero or more classes. As the current
+classifier is very modest, there's only one way to assign a class (based on vendor class
+option), the ability to assign more than one class to a packet is not yet exercised.
+Nevertheless, there is such a possibility and it will be used in a near future. To
+check whether a packet belongs to given class, isc::dhcp::Pkt4::inClass method should
+be used.
+
+The code sometimes refers to this classification as "simple" or 'built-in", because
+it does not require any configuration and thus is built into the server logic.
+
+@subsection dhcpv4ClassifierFull Full Client Classification in DHCPv4
+
+Kea 1.0 introduced full client classification. Each client class consists of a name
+and an expression that can be evaluated on an incoming packet. If it evaluates to
+true, this packet is considered a member of said class. Class definitions are stored
+in isc::dhcp::ClientClassDef objects that are kept in isc::dhcp::ClientClassDictionary.
+This is convenient as there are often multiple classes associated with a given scope.
+As of Kea 1.0, the only supported scope is global, but there are plans to support
+class definitions that are subnet specific.
+
+Client classification is done in isc::dhcp::Dhcpv4Srv::classifyPacket. First, the old
+"built-in" (see @ref dhcpv4ClassifierSimple) classification is called. Then the code
+iterates over all class definitions and for each class definition it calls
+isc::dhcp::evaluate, which is implemented in libeval (see @ref libeval). If the
+evaluation is successful, the class name is added to the packet (by calling
+isc::dhcp::pkt::addClass).
+
+If packet belongs to at least one class, this fact is logged. If there are any
+exceptions raised during class evaluation, an error is logged and the code attempts
+to evaluate the next class.
+
+@subsection dhcpv4ClassifierUsage How client classification information is used in DHCPv4
+
+The classification code has been revamped in Kea 1.1. The old code that did specific
+things for cable modems can now be achieved with the general classification code. Users
+can simply define the class with next-address and/or filename in it.
+
+It is possible to define class restrictions in subnet, so a given subnet is only
+accessible to clients that belong to a given class. That is implemented as
+isc::dhcp::Pkt4::classes_ being passed in isc::dhcp::Dhcpv4Srv::selectSubnet()
+to isc::dhcp::CfgMgr::getSubnet4(). Currently this capability is usable, but
+the number of scenarios it supports is limited.
+
+Finally, it is possible to define client class-specific options, so clients belonging
+to a class foo, will get options associated with class foo. This is implemented in
+isc::dhcp::Dhcpv4Srv::buildCfgOptionList.
+
+@section dhcpv4ConfigBackend Configuration backend for DHCPv4
+
+Earlier Kea versions had a concept of backends, which were implementations of
+different ways how configuration could be delivered to Kea. It seems that the
+concept of backends didn't get much enthusiasm from users and having multiple
+backends was cumbersome to maintain, so it was removed in 1.0.
+
+@section dhcpv4SignalBasedReconfiguration Reconfiguring DHCPv4 server with SIGHUP signal
+
+Online reconfiguration (reconfiguration without a need to restart the server) is an
+important feature which is supported by all modern DHCP servers. When using the JSON
+configuration backend, a configuration file name is specified with a command line
+option of the DHCP server binary. The configuration file is used to configure the
+server at startup. If the initial configuration fails, the server will fail to start.
+If the server starts and configures successfully it will use the initial configuration
+until it is reconfigured.
+
+The reconfiguration request can be triggered externally (from other process) by editing
+a configuration file and sending a SIGHUP signal to DHCP server process. After receiving
+the SIGHUP signal, the server will re-read the configuration file specified at startup.
+If the reconfiguration fails, the server will continue to run and use the last good
+configuration.
+
+The signal handler for SIGHUP (also for SIGTERM and SIGINT) are installed in the
+kea_controller.cc using the @c isc::util::SignalSet class. The
+@c isc::dhcp::Dhcp4Srv calls @c isc::dhcp::Daemon::handleSignal on each pass
+through the main loop. This method fetches the last received signal and calls
+a handler function defined in the kea_controller.cc. The handler function
+calls a static function @c configure defined in the kea_controller.cc.
+
+The signal handler reconfigures the server using the configuration file
+specified at server startup. The location of this file is held in the
+@c Daemon class.
+
+@section dhcpv4Other Other DHCPv4 topics
+
+For hooks API support in DHCPv4, see @ref dhcpv4Hooks.
+
+For a description of how DHCPv4-over-DHCPv6 is implemented, see @subpage dhcpv4o6Dhcp4.
+
+*/
diff --git a/src/bin/dhcp4/dhcp4_hooks.dox b/src/bin/dhcp4/dhcp4_hooks.dox
new file mode 100644
index 0000000..47a490b
--- /dev/null
+++ b/src/bin/dhcp4/dhcp4_hooks.dox
@@ -0,0 +1,484 @@
+// Copyright (C) 2013-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/.
+
+/**
+ @page dhcpv4Hooks The Hooks API for the DHCPv4 Server
+
+ @section dhcpv4HooksIntroduction Introduction
+ Kea features an API (the "Hooks" API) that allows user-written code to
+ be integrated into Kea and called at specific points in its processing.
+ An overview of the API and a tutorial for writing such code can be found in
+ the @ref hooksdgDevelopersGuide. It also includes information how hooks
+ framework can be used to implement additional control commands for the
+ DHCPv4 server. Information for Kea maintainers can be found in the
+ @ref hooksComponentDeveloperGuide.
+
+ This manual is more specialized and is aimed at developers of hook
+ code for the DHCPv4 server. It describes each hook point, what the callouts
+ attached to the hook are able to do, and the arguments passed to the
+ callouts. Each entry in this manual has the following information:
+
+ - Name of the hook point.
+ - Arguments for the callout. As well as the argument name and data type, the
+ information includes the direction, which can be one of:
+ - @b in - the server passes values to the callout but ignored any data
+ returned.
+ - @b out - the callout is expected to set this value.
+ - <b>in/out</b> - the server passes a value to the callout and uses whatever
+ value the callout sends back. Note that the callout may choose not to
+ do any modification, in which case the server will use whatever value
+ it sent to the callout.
+ - Description of the hook. This explains where in the processing the hook
+ is located, the possible actions a callout attached to this hook could take,
+ and a description of the data passed to the callouts.
+ - Next step status: the action taken by the server when a callout chooses to set
+ status to specified value. Actions not listed explicitly are not supported.
+ If a callout sets status to unsupported value, this specific value will be
+ ignored and treated as if the status was CONTINUE.
+
+@section dhcpv4HooksHookPoints Hooks in the DHCPv4 Server
+
+The following list is roughly ordered by appearance of specific hook points during
+packet processing, but the exact order depends on the actual processing. Hook points
+that are not specific to packet processing (e.g. lease expiration) will be added
+to the end of this list.
+
+ @subsection dhcp4HooksDhcpv4SrvConfigured dhcp4_srv_configured
+ - @b Arguments:
+ - name: @b io_context, type: isc::asiolink::IOServicePtr, direction: <b>in</b>
+ - name: @b network_state, type: isc::dhcp::NetworkStatePtr, direction: <b>in</b>
+ - name: @b json_config, type: isc::data::ConstElementPtr, direction: <b>in</b>
+ - name: @b server_config, type: isc::dhcp::SrvConfigPtr, direction: <b>in</b>
+
+ - @b Description: this callout is executed when the server has completed
+ its (re)configuration. The server provides received and parsed configuration
+ structures to the hook library. It also provides a pointer to the IOService
+ object which is used by the server to run asynchronous operations. The hooks
+ libraries can use this IOService object to schedule asynchronous tasks which
+ are triggered by the DHCP server's main loop. The hook library should hold the
+ provided pointer until the library is unloaded. The NetworkState object
+ provides access to the DHCP service state of the server and allows for
+ enabling and disabling the DHCP service from the hooks libraries.
+
+ - <b>Next step status</b>: If any callout sets the status to DROP, the server
+ will interrupt the reconfiguration process. The hook callout is expected to
+ have completed the cause of the interruption under the "error" handle argument
+ with a std::string which is then logged. Finally, this leads to the
+ termination of kea-dhcp4.
+
+ @subsection dhcpv4HooksCb4Update cb4_updated
+ - @b Arguments:
+ - name: audit_entries, type isc::db::AuditEntryCollectionPtr, direction: <b>in</b>
+
+ - @b Description: this callout is executed when the server has completed
+ a configuration update using the Config Backend. The server provides
+ the audit entries as a never null pointer to a not empty collection
+ copied from the update apply method argument.
+
+ - <b>Next step status</b>: Status codes returned by the callouts installed on
+ this hook point are ignored.
+
+ @subsection dhcpv4HooksBuffer4Receive buffer4_receive
+
+ - @b Arguments:
+ - name: @b query4, type: isc::dhcp::Pkt4Ptr, direction: <b>in/out</b>
+
+ - @b Description: this callout is executed when the server has received a
+ buffer containing a DHCPv4 message, but the message hasn't yet been parsed.
+ The sole argument "query4" contains a pointer to the isc::dhcp::Pkt4
+ object, which contains the source and destination address of the
+ received packet, the interface over which the packet has been received, and
+ a raw buffer, stored in the data_ field, containing the DHCPv4 message
+ in the wire format. None of the packet fields (op_, hlen_, chaddr_, etc.)
+ are set yet. Callouts installed on this hook point can modify the data
+ in the received buffer. The server will parse the buffer afterwards.
+
+ - <b>Next step status</b>: If any callout sets the status to DROP, the server
+ will drop the packet and start processing the next one.
+ If any callout sets the status to SKIP, the server will
+ skip the buffer parsing. In this case there is an expectation that
+ the callout will parse the options carried in the buffer, create
+ @c isc::dhcp::Option objects (or derived) and add them to the "query4"
+ object using the @c isc::dhcp::Pkt4::addOption.
+ Otherwise the server will find out that some mandatory options are
+ missing (e.g. DHCP Message Type) and will drop the message.
+
+ @subsection dhcpv4HooksPkt4Receive pkt4_receive
+
+ - @b Arguments:
+ - name: @b query4, type: isc::dhcp::Pkt4Ptr, direction: <b>in/out</b>
+
+ - @b Description: this callout is executed when an incoming DHCPv4
+ packet is received and its content has been parsed. The sole
+ argument "query4" contains a pointer to an isc::dhcp::Pkt4 object
+ that contains all information regarding incoming packet, including
+ its source and destination addresses, interface over which it was
+ received, a list of all options present within and the relay
+ information. All fields of the Pkt4 object can be modified at this
+ time. By the time this hook is reached, the contents of the data_
+ field has been already parsed and stored in other fields. Therefore,
+ the modification in the data_ field has no effect.
+
+ - <b>Next step status</b>: If any callout sets the status to SKIP or DROP, the server will
+ drop the packet and start processing the next one. The reason for the drop
+ will be logged if logging is set to the appropriate debug level.
+
+@subsection dhcpv4HooksSubnet4Select subnet4_select
+
+ - @b Arguments:
+ - name: @b query4, type: isc::dhcp::Pkt4Ptr, direction: <b>in/out</b>
+ - name: @b subnet4, type: isc::dhcp::Subnet4Ptr, direction: <b>in/out</b>
+ - name: @b subnet4collection, type: const isc::dhcp::Subnet4Collection *,
+ direction: <b>in</b>
+
+ - @b Description: this callout is executed when a subnet is being
+ selected for the incoming packet. All parameters and addresses
+ will be assigned from that subnet. A callout can select a
+ different subnet if it wishes so. The list of all subnets currently
+ configured are provided as "subnet4collection". The list itself must
+ not be modified.
+
+ - <b>Next step status</b>: If any callout installed on the "subnet4_select" hook
+ sets the next step status to DROP, the server cancels current processing,
+ drop the packet and start processing the next one.
+ If any callout sets the status to SKIP, the server will not select any subnet.
+ Packet processing will continue, but will be severely limited.
+
+@subsection dhcpv4HooksHost4Identifier host4_identifier
+
+ - @b Arguments:
+ - name: @b query4, type isc::dhcp::Pkt4Ptr, direction: <b>in</b>
+ - name: @b id_type, type isc::dhcp::Host::IdentifierType, direction: <b>in/out</b>
+ - name: @b id_value, type std::vector<uint8_t>, direction: <b>out</b>
+
+ - @b Description: this callout is executed only if flexible identifiers are
+ enabled, i.e. host-reservation-identifiers contain 'flex-id' value. This
+ callout enables external library to provide values for flexible identifiers.
+ To be able to use this feature, flex_id hook library is required.
+
+ - <b>Next step status</b>: If a callout installed on the "host4_identifier" hook
+ point sets the next step status to value other than NEXT_STEP_CONTINUE, the
+ identifier will not be used.
+
+When the "early-global-reservations-lookup" flag is true this callout is
+called before "subnet4_select".
+
+@subsection dhcpv4HooksLeaseSelect lease4_select
+
+ - @b Arguments:
+ - name: @b query4, type: isc::dhcp::Pkt4Ptr, direction: <b>in</b>
+ - name: @b subnet4, type: isc::dhcp::Subnet4Ptr, direction: <b>in</b>
+ - name: @b fake_allocation, type: bool, direction: <b>in</b>
+ - name: @b lease4, type: isc::dhcp::Lease4Ptr, direction: <b>in/out</b>
+
+ - @b Description: this callout is executed after the server engine
+ has selected a lease for the client's request, but before the lease has
+ been inserted into the database. Any modifications made to the
+ "lease4" object will affect the lease's record in the database.
+ The callout should sanity check all modifications as the server will
+ use that data as is, with no further checking.\n\n
+ The server processes lease requests for DHCPDISCOVER and DHCPREQUEST in a
+ very similar way. The only major difference is that for DHCPDISCOVER
+ the lease is only selected, but not inserted into the database. The callouts
+ may distinguish between DHCPDISCOVER and DHCPREQUEST by checking the
+ value of the "fake_allocation" flag: a value of true indicates that the
+ lease won't be inserted into the database (DHCPDISCOVER case), a value of
+ false indicates that it will (DHCPREQUEST case).
+
+ - <b>Next step status</b>: If any callout installed on the "lease4_select" hook
+ sets the next step action to SKIP, the server will not assign any lease and
+ the callouts become responsible for the lease assignment. If the callouts
+ fail to provide a lease, the packet processing will continue, but client
+ will not get an address.
+
+@subsection dhcpv4HooksLeaseRenew lease4_renew
+
+ - @b Arguments:
+ - name: @b query4, type: isc::dhcp::Pkt4Ptr, direction: <b>in</b>
+ - name: @b subnet4, type: isc::dhcp::Subnet4Ptr, direction: <b>in</b>
+ - name: @b clientid, type: isc::dhcp::ClientId, direction: <b>in</b>
+ - name: @b hwaddr, type: isc::dhcp::HWAddr, direction: <b>in</b>
+ - name: @b lease4, type: isc::dhcp::Lease4Ptr, direction: <b>in/out</b>
+
+ - @b Description: this callout is executed when the server engine
+ is about to renew a lease, as a result of receiving DHCPREQUEST/Renewing
+ packet. The "lease4" argument points to @c isc::dhcp::Lease4 object that
+ contains the updated values. Callout can modify those values. Care should
+ be taken as the server will attempt to update the lease in the database
+ without any additional checks.
+
+ - <b>Next step status</b>: If any callout installed on the "lease4_renew" hook
+ sets the next step action to SKIP, the server will not update the lease in the
+ database and will continue using the old values instead.
+
+@subsection dhcpv4HooksLeaseRelease lease4_release
+
+ - @b Arguments:
+ - name: @b query4, type: isc::dhcp::Pkt4Ptr, direction: <b>in</b>
+ - name: @b lease4, type: isc::dhcp::Lease4Ptr, direction: <b>in</b>
+
+ - @b Description: this callout is executed when the server engine
+ is about to release a lease, as a result of receiving DHCPRELEASE packet.
+ The "lease4" argument points to @c Lease4 object that contains the lease to
+ be released. It doesn't make sense to modify it at this time.
+
+ - <b>Next step status</b>: If any callout installed on the "lease4_release" hook
+ sets the next step action to SKIP or DROP, the server will not delete the lease.
+ It will be kept in the database and will go through the regular expiration/reuse
+ process.
+
+@subsection dhcpv4HooksLeaseDecline lease4_decline
+
+ - @b Arguments:
+ - name: @b query4, type: isc::dhcp::Pkt4Ptr, direction: <b>in</b>
+ - name: @b lease4, type: isc::dhcp::Lease4Ptr, direction: <b>in</b>
+
+ - @b Description: this callout is executed when the server engine
+ is about to decline a lease, as a result of receiving DHCPDECLINE packet.
+ The server already sanity checked it (the packet is sane, attempts to decline
+ a lease that is valid and belongs to the client that requests its decline).
+ The "lease4" argument points to @c Lease4 object that contains the lease to
+ be released. Note this lease still contains client identifying information.
+ That data is provided for informational purposes and it doesn't make sense to
+ modify it at this time. All the information will be removed from the lease
+ before it is updated in the database.
+
+ - <b>Next step status</b>: If any callout installed on the "lease4_decline" hook
+ sets the next step action to SKIP or DROP, the server will not decline the lease.
+ Care should be taken when setting this status. The lease will be kept in
+ the database as it is and the client will incorrectly assume that the server
+ marked this lease as unavailable. If the client restarts its configuration,
+ it will get the same (not declined) lease as a result.
+
+@subsection dhcpv4HooksDdns4Update ddns4_update
+
+ - @b Arguments:
+ - name: @b query4, type: isc::dhcp::Pkt4Ptr, direction: <b>in</b>
+ - name: @b response4, type: isc::dhcp::Pkt4Ptr, direction: <b>in</b>
+ - name: @b subnet4, type: isc::dhcp::Subnet4Ptr, direction: <b>in</b>
+ - name: @b hostname, type: std::string, direction: <b>in/out</b>
+ - name: @b fwd_update, type: bool, direction: <b>in/out</b>
+ - name: @b rev_update, type: bool, direction: <b>in/out</b>
+ - name: @b ddns_params, type: isc::dhcp::DdnsParamsPtr, direction: <b>in</b>
+
+ - @b Description: this callout is executed after the server has selected
+ a lease and has formed a host name to associate with the lease and/or use
+ as the basis for the FQDN for DNS updates (if enabled), but before the
+ lease has been committed or any NameChangeRequests have been generated to
+ send to <b>kea-dhcp-ddns</b>. Thus it provides an opportunity to alter the
+ host name as well as whether or not forward and/or reverse updates are
+ enabled.
+
+ Upon entry into the callout, the arguments <b>hostname</b>,<b>fwd_update</b>,
+ and <b>rev_update</b> have been set by the server based on the client packet,
+ and various configuration values (e.g host reservations, DDNS behavioral
+ parameters, etc). Upon return from the callout, any changes to these
+ values will be applied as follows:
+ - If <b>hostname</b> has changed it will be used to update the outbound
+ host name (option 12) if it exists, the output FQDN option (option 81)
+ if it exists, and used as the FQDN sent in DNS updates
+ - Forward DNS update(s) will be done if <b>fwd_update</b> is true (and
+ <b>kea-dhcp-ddns</b> connectivity is enabled)
+ - Reverse DNS update(s) will be done if <b>rev_update</b> is true (and
+ <b>kea-dhcp-ddns</b> connectivity is enabled)
+
+ - <b>Next step status</b>: Not applicable, its value will be ignored.
+
+@subsection dhcpv4Leases4Committed leases4_committed
+
+ - @b Arguments:
+ - name: @b query4, type: isc::dhcp::Pkt4Ptr, direction: <b>in</b>
+ - name: @b leases4, type: isc::dhcp::Lease4CollectionPtr, direction: <b>in</b>
+ - name: @b deleted_leases4, type: isc::dhcp::Lease4CollectionPtr, direction: <b>in</b>
+
+ - @b Description: this callout is executed when the server has applied all
+ lease changes as a result of DHCP message processing. This includes
+ writing new lease into the database, releasing an old lease for this
+ client or declining a lease. This callout is executed only for the
+ DHCP client messages which may cause lease changes, i.e. DHCPREQUEST,
+ DHCPRELEASE and DHCPDECLINE. This callout is not executed for DHCPDISCOVER
+ and DHCPINFORM. If the callouts are executed as a result of DHCPREQUEST
+ message, it is possible that both leases collections hold leases to be
+ handled. This is the case when the new lease allocation replaces an existing
+ lease for the client. The "deleted_leases4" object will hold a previous
+ lease instance and the "leases4" object will hold the new lease for this
+ client. The callouts should be prepared to handle such situation. When
+ the callout is executed as a result DHCPRELEASE, the callout will typically
+ receive only one lease (being released) in the "deleted_leases4" object.
+ Both leases collections are always provided to the callouts,
+ even though they may sometimes be empty.
+
+ - <b>Next step status</b>: If any callout installed on the "leases4_committed"
+ sets the next step action to DROP the server will drop the processed query.
+ If it sets the next step action to PARK, the server will park the processed
+ packet (hold packet processing) until the hook libraries explicitly unpark
+ the packet after they are done performing asynchronous operations.
+
+
+@subsection dhcpv4HooksPkt4Send pkt4_send
+
+ - @b Arguments:
+ - name: @b response4, type: isc::dhcp::Pkt4Ptr, direction: <b>in/out</b>
+ - name: @b query4, type: isc::dhcp::Pkt4Ptr, direction: <b>in</b>
+
+ - @b Description: this callout is executed when server's response
+ is about to be sent back to the client. The sole argument "response4"
+ contains a pointer to an isc::dhcp::Pkt4 object carrying the
+ packet, with source and destination addresses set, interface over which
+ it will be sent, and a list of all options and relay information. All fields
+ of the @c Pkt4 object can be modified at this time, except @c buffer_out_.
+ (This is scratch space used for constructing the packet after all
+ pkt4_send callouts are complete, so any changes to that field will
+ be overwritten.)\n\n
+ The argument query4 contains a pointer to the corresponding query packet
+ (allowing to perform correlation between response and query). This object
+ cannot be modified.
+
+ - <b>Next step action</b>: if any callout installed on the "pkt4_send" hook
+ sets the next step action to SKIP, the server will not construct the raw
+ buffer. The expectation is that if the callout set skip flag, it is
+ responsible for constructing raw form on its own. Otherwise the output
+ packet will be sent with zero length. If any callout set the next step action
+ to DROP, the server will drop the packet.
+
+@subsection dhcpv4HooksBuffer4Send buffer4_send
+
+ - @b Arguments:
+ - name: @b response4, type: isc::dhcp::Pkt4Ptr, direction: <b>in/out</b>
+
+ - @b Description: this callout is executed when server's response
+ is about to be sent back to the client. The sole argument "response4"
+ contains a pointer to an @c isc::dhcp::Pkt4 object that contains the
+ packet, with source and destination addresses set, interface over which
+ it will be sent, and a list of all options and relay information. The raw
+ on-wire form is already prepared in @c buffer_out_ (see
+ @c isc::dhcp::Pkt4::getBuffer())
+ Callouts should not modify the packet fields or options contents at this
+ time, because they were already used to construct on-wire buffer. Their
+ modification would have no effect.
+
+ - <b>Next step status</b>: if any callout sets the next step action to SKIP or DROP,
+ the server will drop this response packet. However, the original request
+ packet from a client was processed, so server's state most likely has changed
+ (e.g. lease was allocated). Setting this flag merely stops the change
+ being communicated to the client.
+
+@subsection dhcpv4HooksLease4Expire lease4_expire
+
+- @b Arguments:
+ - name: @b lease4, type: isc::dhcp::Lease4Ptr, direction: <b>in/out</b>
+ - name: @b remove_lease, type: bool, direction: <b>in</b>
+
+- @b Description: this callout is executed for each expired lease when
+ the server performs reclamation of the expired leases. During this
+ process the server executes "lease4_expire" callout, removes the DNS
+ records associated with this lease and finally removes the lease from
+ the database or updates its status to "expired-reclaimed". The "lease4"
+ argument contains the pointer to the lease being reclaimed. The second
+ argument "remove_lease" indicates if the reclaimed leases should be
+ removed from the lease database (if true), or their state should be
+ set to "expired-reclaimed" in the lease database. This argument
+ is only used by the callout if it takes responsibility for the lease
+ reclamation, i.e. it sets the "skip" flag to "true". The "remove_lease"
+ argument is set to "true" if the "flush-reclaimed-timer-wait-time" is
+ set to 0 in the server configuration file.
+
+- <b>Next step status</b>: if the callout sets the next step action to SKIP,
+ the server will assume that the callout has fully reclaimed the lease, i.e.
+ performed the DNS update and updated the lease in the database. The
+ server will not perform any further actions on the lease for which the
+ skip flag has been set. It is important to note that if the callout
+ sets this flag but fails to reclaim the lease in the database, the
+ reclamation routine will repeatedly process this lease in subsequent
+ runs. Therefore, the implementors of this callout must make sure that
+ skip flag is only set when the lease has been actually reclaimed in the
+ database by the callout.
+
+@subsection dhcpv4HooksLease4Recover lease4_recover
+
+- @b Arguments:
+ - name: @b lease4, type: isc::dhcp::Lease4Ptr, direction: <b>in</b>
+
+- @b Description: this callout is executed for each declined lease that
+ has expired (was put aside for the duration of decline-probation-period)
+ and is being recovered. The lease has already been stripped
+ from any client identifying information when it was put into declined
+ state. In principle the callouts can modify the lease in this hook,
+ but it makes little sense. There's no useful data in the lease, except
+ the IPv4 address (which must not be modified).
+
+- <b>Next step status</b>: if the callout sets the next step action to SKIP,
+ the server will skip the lease recovery. In other words, it will keep
+ the lease as is. This is not recommended in general, as the declined
+ expired leases will remain in the database and their recovery will
+ be attempted during the next reclaim cycle.
+
+@subsection dhcpv4HooksCommandProcessed command_processed
+
+ - @b Arguments:
+ - name: @b name, type: std::string, direction: <b>in</b>
+ - name: @b arguments type: isc::data::ConstElementPtr, direction: <b>in</b>
+ - name: @b response, type: isc::data::ConstElementPtr, direction: <b>in/out</b>
+
+ - @b Description: this callout is executed after the DHCPv4 server receives
+ and processes a control command over the command channel (typically unix domain socket).
+ The "name" argument is the name of the command processed.
+ The "arguments" argument is a pointer to the parsed JSON structure
+ containing the command's input arguments. The "response" argument
+ is the parsed JSON structure containing the response generated by
+ the command processing.
+
+ - <b>Next step status</b>: Not applicable, its value will be ignored.
+
+@section dhcpv4HooksOptionsAccess Accessing DHCPv4 Options within a Packet
+When the server constructs a response message to a client it includes
+DHCP options configured for this client in a response message. Apart
+from the dynamically created options, such as Client FQDN option, it
+typically includes many options specified in the server configuration
+and held within the configuration structures by @c CfgMgr. Option
+instances are created once, during server configuration, and the
+@c CfgMgr holds pointers to those instances until the next server
+reconfiguration.
+
+When the server includes an option in a response message it copies
+a pointer to the instance of this option, rather than entire option.
+This ensures the good performance of response message creation. However,
+it also implies that any modification to the option carried in the
+DHCP response will affect an instance of this option in the server
+configuration structures. This is obviously not desired as it would
+affect all subsequent DHCP transactions involving this option. The
+DHCP server code avoids modifying the options included in the messages
+so it is possible to ensure good performance without a risk of
+accidentally modifying server configuration. The situation is
+different with hooks libraries which purpose is, in many cases,
+to modify values of options inserted by the server.
+
+Thus, @c Pkt class provides a mechanism to return a copy of an
+option to a caller (e.g. a callout), rather than an instance
+shared with the @c CfgMgr. This mechanism is enabled for all instances
+of @c Pkt4 passed to the callouts, i.e. "query4" and "response4"
+arguments. It is also automatically disabled when the callout
+returns the control back to the server.
+
+At every hook point, where the server passes an instance of a packet
+to the callouts, the server calls
+@c isc::dhcp::Pkt4::setCopyRetrievedOptions (true)
+to force copying options retrieved by @c isc::dhcp::Pkt4::getOption
+within callouts. The copied option replaces an original option within a
+packet and any modification to the option content by the callout
+would only affect the option instance associated with the packet.
+
+On the other hand, copying each retrieved option may be expensive.
+If performance of a hook library is a concern, it is possible for the
+hook library to disable copying retrieved options by calling
+@c isc::dhcp::Pkt4::setCopyRetrievedOptions (false) within a callout.
+In this case however, the hook library implementer must be aware that any
+modification of the option instance would affect the server configuration
+and may disrupt server's operation. Thus, disabling copying of retrieved
+options is not recommended unless the hook library is not intended
+to modify configured options carried within a packet.
+
+*/
diff --git a/src/bin/dhcp4/dhcp4_lexer.cc b/src/bin/dhcp4/dhcp4_lexer.cc
new file mode 100644
index 0000000..4190ba2
--- /dev/null
+++ b/src/bin/dhcp4/dhcp4_lexer.cc
@@ -0,0 +1,6518 @@
+#line 1 "dhcp4_lexer.cc"
+
+#line 3 "dhcp4_lexer.cc"
+
+#define YY_INT_ALIGNED short int
+
+/* A lexical scanner generated by flex */
+
+/* %not-for-header */
+/* %if-c-only */
+/* %if-not-reentrant */
+#define yy_create_buffer parser4__create_buffer
+#define yy_delete_buffer parser4__delete_buffer
+#define yy_scan_buffer parser4__scan_buffer
+#define yy_scan_string parser4__scan_string
+#define yy_scan_bytes parser4__scan_bytes
+#define yy_init_buffer parser4__init_buffer
+#define yy_flush_buffer parser4__flush_buffer
+#define yy_load_buffer_state parser4__load_buffer_state
+#define yy_switch_to_buffer parser4__switch_to_buffer
+#define yypush_buffer_state parser4_push_buffer_state
+#define yypop_buffer_state parser4_pop_buffer_state
+#define yyensure_buffer_stack parser4_ensure_buffer_stack
+#define yy_flex_debug parser4__flex_debug
+#define yyin parser4_in
+#define yyleng parser4_leng
+#define yylex parser4_lex
+#define yylineno parser4_lineno
+#define yyout parser4_out
+#define yyrestart parser4_restart
+#define yytext parser4_text
+#define yywrap parser4_wrap
+#define yyalloc parser4_alloc
+#define yyrealloc parser4_realloc
+#define yyfree parser4_free
+
+/* %endif */
+/* %endif */
+/* %ok-for-header */
+
+#define FLEX_SCANNER
+#define YY_FLEX_MAJOR_VERSION 2
+#define YY_FLEX_MINOR_VERSION 6
+#define YY_FLEX_SUBMINOR_VERSION 4
+#if YY_FLEX_SUBMINOR_VERSION > 0
+#define FLEX_BETA
+#endif
+
+/* %if-c++-only */
+/* %endif */
+
+/* %if-c-only */
+#ifdef yy_create_buffer
+#define parser4__create_buffer_ALREADY_DEFINED
+#else
+#define yy_create_buffer parser4__create_buffer
+#endif
+
+#ifdef yy_delete_buffer
+#define parser4__delete_buffer_ALREADY_DEFINED
+#else
+#define yy_delete_buffer parser4__delete_buffer
+#endif
+
+#ifdef yy_scan_buffer
+#define parser4__scan_buffer_ALREADY_DEFINED
+#else
+#define yy_scan_buffer parser4__scan_buffer
+#endif
+
+#ifdef yy_scan_string
+#define parser4__scan_string_ALREADY_DEFINED
+#else
+#define yy_scan_string parser4__scan_string
+#endif
+
+#ifdef yy_scan_bytes
+#define parser4__scan_bytes_ALREADY_DEFINED
+#else
+#define yy_scan_bytes parser4__scan_bytes
+#endif
+
+#ifdef yy_init_buffer
+#define parser4__init_buffer_ALREADY_DEFINED
+#else
+#define yy_init_buffer parser4__init_buffer
+#endif
+
+#ifdef yy_flush_buffer
+#define parser4__flush_buffer_ALREADY_DEFINED
+#else
+#define yy_flush_buffer parser4__flush_buffer
+#endif
+
+#ifdef yy_load_buffer_state
+#define parser4__load_buffer_state_ALREADY_DEFINED
+#else
+#define yy_load_buffer_state parser4__load_buffer_state
+#endif
+
+#ifdef yy_switch_to_buffer
+#define parser4__switch_to_buffer_ALREADY_DEFINED
+#else
+#define yy_switch_to_buffer parser4__switch_to_buffer
+#endif
+
+#ifdef yypush_buffer_state
+#define parser4_push_buffer_state_ALREADY_DEFINED
+#else
+#define yypush_buffer_state parser4_push_buffer_state
+#endif
+
+#ifdef yypop_buffer_state
+#define parser4_pop_buffer_state_ALREADY_DEFINED
+#else
+#define yypop_buffer_state parser4_pop_buffer_state
+#endif
+
+#ifdef yyensure_buffer_stack
+#define parser4_ensure_buffer_stack_ALREADY_DEFINED
+#else
+#define yyensure_buffer_stack parser4_ensure_buffer_stack
+#endif
+
+#ifdef yylex
+#define parser4_lex_ALREADY_DEFINED
+#else
+#define yylex parser4_lex
+#endif
+
+#ifdef yyrestart
+#define parser4_restart_ALREADY_DEFINED
+#else
+#define yyrestart parser4_restart
+#endif
+
+#ifdef yylex_init
+#define parser4_lex_init_ALREADY_DEFINED
+#else
+#define yylex_init parser4_lex_init
+#endif
+
+#ifdef yylex_init_extra
+#define parser4_lex_init_extra_ALREADY_DEFINED
+#else
+#define yylex_init_extra parser4_lex_init_extra
+#endif
+
+#ifdef yylex_destroy
+#define parser4_lex_destroy_ALREADY_DEFINED
+#else
+#define yylex_destroy parser4_lex_destroy
+#endif
+
+#ifdef yyget_debug
+#define parser4_get_debug_ALREADY_DEFINED
+#else
+#define yyget_debug parser4_get_debug
+#endif
+
+#ifdef yyset_debug
+#define parser4_set_debug_ALREADY_DEFINED
+#else
+#define yyset_debug parser4_set_debug
+#endif
+
+#ifdef yyget_extra
+#define parser4_get_extra_ALREADY_DEFINED
+#else
+#define yyget_extra parser4_get_extra
+#endif
+
+#ifdef yyset_extra
+#define parser4_set_extra_ALREADY_DEFINED
+#else
+#define yyset_extra parser4_set_extra
+#endif
+
+#ifdef yyget_in
+#define parser4_get_in_ALREADY_DEFINED
+#else
+#define yyget_in parser4_get_in
+#endif
+
+#ifdef yyset_in
+#define parser4_set_in_ALREADY_DEFINED
+#else
+#define yyset_in parser4_set_in
+#endif
+
+#ifdef yyget_out
+#define parser4_get_out_ALREADY_DEFINED
+#else
+#define yyget_out parser4_get_out
+#endif
+
+#ifdef yyset_out
+#define parser4_set_out_ALREADY_DEFINED
+#else
+#define yyset_out parser4_set_out
+#endif
+
+#ifdef yyget_leng
+#define parser4_get_leng_ALREADY_DEFINED
+#else
+#define yyget_leng parser4_get_leng
+#endif
+
+#ifdef yyget_text
+#define parser4_get_text_ALREADY_DEFINED
+#else
+#define yyget_text parser4_get_text
+#endif
+
+#ifdef yyget_lineno
+#define parser4_get_lineno_ALREADY_DEFINED
+#else
+#define yyget_lineno parser4_get_lineno
+#endif
+
+#ifdef yyset_lineno
+#define parser4_set_lineno_ALREADY_DEFINED
+#else
+#define yyset_lineno parser4_set_lineno
+#endif
+
+#ifdef yywrap
+#define parser4_wrap_ALREADY_DEFINED
+#else
+#define yywrap parser4_wrap
+#endif
+
+/* %endif */
+
+#ifdef yyalloc
+#define parser4_alloc_ALREADY_DEFINED
+#else
+#define yyalloc parser4_alloc
+#endif
+
+#ifdef yyrealloc
+#define parser4_realloc_ALREADY_DEFINED
+#else
+#define yyrealloc parser4_realloc
+#endif
+
+#ifdef yyfree
+#define parser4_free_ALREADY_DEFINED
+#else
+#define yyfree parser4_free
+#endif
+
+/* %if-c-only */
+
+#ifdef yytext
+#define parser4_text_ALREADY_DEFINED
+#else
+#define yytext parser4_text
+#endif
+
+#ifdef yyleng
+#define parser4_leng_ALREADY_DEFINED
+#else
+#define yyleng parser4_leng
+#endif
+
+#ifdef yyin
+#define parser4_in_ALREADY_DEFINED
+#else
+#define yyin parser4_in
+#endif
+
+#ifdef yyout
+#define parser4_out_ALREADY_DEFINED
+#else
+#define yyout parser4_out
+#endif
+
+#ifdef yy_flex_debug
+#define parser4__flex_debug_ALREADY_DEFINED
+#else
+#define yy_flex_debug parser4__flex_debug
+#endif
+
+#ifdef yylineno
+#define parser4_lineno_ALREADY_DEFINED
+#else
+#define yylineno parser4_lineno
+#endif
+
+/* %endif */
+
+/* First, we deal with platform-specific or compiler-specific issues. */
+
+/* begin standard C headers. */
+/* %if-c-only */
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+/* %endif */
+
+/* %if-tables-serialization */
+/* %endif */
+/* end standard C headers. */
+
+/* %if-c-or-c++ */
+/* flex integer type definitions */
+
+#ifndef FLEXINT_H
+#define FLEXINT_H
+
+/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */
+
+#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
+
+/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h,
+ * if you want the limit (max/min) macros for int types.
+ */
+#ifndef __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS 1
+#endif
+
+#include <inttypes.h>
+typedef int8_t flex_int8_t;
+typedef uint8_t flex_uint8_t;
+typedef int16_t flex_int16_t;
+typedef uint16_t flex_uint16_t;
+typedef int32_t flex_int32_t;
+typedef uint32_t flex_uint32_t;
+#else
+typedef signed char flex_int8_t;
+typedef short int flex_int16_t;
+typedef int flex_int32_t;
+typedef unsigned char flex_uint8_t;
+typedef unsigned short int flex_uint16_t;
+typedef unsigned int flex_uint32_t;
+
+/* Limits of integral types. */
+#ifndef INT8_MIN
+#define INT8_MIN (-128)
+#endif
+#ifndef INT16_MIN
+#define INT16_MIN (-32767-1)
+#endif
+#ifndef INT32_MIN
+#define INT32_MIN (-2147483647-1)
+#endif
+#ifndef INT8_MAX
+#define INT8_MAX (127)
+#endif
+#ifndef INT16_MAX
+#define INT16_MAX (32767)
+#endif
+#ifndef INT32_MAX
+#define INT32_MAX (2147483647)
+#endif
+#ifndef UINT8_MAX
+#define UINT8_MAX (255U)
+#endif
+#ifndef UINT16_MAX
+#define UINT16_MAX (65535U)
+#endif
+#ifndef UINT32_MAX
+#define UINT32_MAX (4294967295U)
+#endif
+
+#ifndef SIZE_MAX
+#define SIZE_MAX (~(size_t)0)
+#endif
+
+#endif /* ! C99 */
+
+#endif /* ! FLEXINT_H */
+
+/* %endif */
+
+/* begin standard C++ headers. */
+/* %if-c++-only */
+/* %endif */
+
+/* TODO: this is always defined, so inline it */
+#define yyconst const
+
+#if defined(__GNUC__) && __GNUC__ >= 3
+#define yynoreturn __attribute__((__noreturn__))
+#else
+#define yynoreturn
+#endif
+
+/* %not-for-header */
+/* Returned upon end-of-file. */
+#define YY_NULL 0
+/* %ok-for-header */
+
+/* %not-for-header */
+/* Promotes a possibly negative, possibly signed char to an
+ * integer in range [0..255] for use as an array index.
+ */
+#define YY_SC_TO_UI(c) ((YY_CHAR) (c))
+/* %ok-for-header */
+
+/* %if-reentrant */
+/* %endif */
+
+/* %if-not-reentrant */
+
+/* %endif */
+
+/* Enter a start condition. This macro really ought to take a parameter,
+ * but we do it the disgusting crufty way forced on us by the ()-less
+ * definition of BEGIN.
+ */
+#define BEGIN (yy_start) = 1 + 2 *
+/* Translate the current start state into a value that can be later handed
+ * to BEGIN to return to the state. The YYSTATE alias is for lex
+ * compatibility.
+ */
+#define YY_START (((yy_start) - 1) / 2)
+#define YYSTATE YY_START
+/* Action number for EOF rule of a given start state. */
+#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1)
+/* Special action meaning "start processing a new file". */
+#define YY_NEW_FILE yyrestart( yyin )
+#define YY_END_OF_BUFFER_CHAR 0
+
+/* Size of default input buffer. */
+#ifndef YY_BUF_SIZE
+#ifdef __ia64__
+/* On IA-64, the buffer size is 16k, not 8k.
+ * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case.
+ * Ditto for the __ia64__ case accordingly.
+ */
+#define YY_BUF_SIZE 32768
+#else
+#define YY_BUF_SIZE 16384
+#endif /* __ia64__ */
+#endif
+
+/* The state buf must be large enough to hold one state per character in the main buffer.
+ */
+#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type))
+
+#ifndef YY_TYPEDEF_YY_BUFFER_STATE
+#define YY_TYPEDEF_YY_BUFFER_STATE
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+#endif
+
+#ifndef YY_TYPEDEF_YY_SIZE_T
+#define YY_TYPEDEF_YY_SIZE_T
+typedef size_t yy_size_t;
+#endif
+
+/* %if-not-reentrant */
+extern int yyleng;
+/* %endif */
+
+/* %if-c-only */
+/* %if-not-reentrant */
+extern FILE *yyin, *yyout;
+/* %endif */
+/* %endif */
+
+#define EOB_ACT_CONTINUE_SCAN 0
+#define EOB_ACT_END_OF_FILE 1
+#define EOB_ACT_LAST_MATCH 2
+
+ #define YY_LESS_LINENO(n)
+ #define YY_LINENO_REWIND_TO(ptr)
+
+/* Return all but the first "n" matched characters back to the input stream. */
+#define yyless(n) \
+ do \
+ { \
+ /* Undo effects of setting up yytext. */ \
+ int yyless_macro_arg = (n); \
+ YY_LESS_LINENO(yyless_macro_arg);\
+ *yy_cp = (yy_hold_char); \
+ YY_RESTORE_YY_MORE_OFFSET \
+ (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \
+ YY_DO_BEFORE_ACTION; /* set up yytext again */ \
+ } \
+ while ( 0 )
+#define unput(c) yyunput( c, (yytext_ptr) )
+
+#ifndef YY_STRUCT_YY_BUFFER_STATE
+#define YY_STRUCT_YY_BUFFER_STATE
+struct yy_buffer_state
+ {
+/* %if-c-only */
+ FILE *yy_input_file;
+/* %endif */
+
+/* %if-c++-only */
+/* %endif */
+
+ char *yy_ch_buf; /* input buffer */
+ char *yy_buf_pos; /* current position in input buffer */
+
+ /* Size of input buffer in bytes, not including room for EOB
+ * characters.
+ */
+ int yy_buf_size;
+
+ /* Number of characters read into yy_ch_buf, not including EOB
+ * characters.
+ */
+ int yy_n_chars;
+
+ /* Whether we "own" the buffer - i.e., we know we created it,
+ * and can realloc() it to grow it, and should free() it to
+ * delete it.
+ */
+ int yy_is_our_buffer;
+
+ /* Whether this is an "interactive" input source; if so, and
+ * if we're using stdio for input, then we want to use getc()
+ * instead of fread(), to make sure we stop fetching input after
+ * each newline.
+ */
+ int yy_is_interactive;
+
+ /* Whether we're considered to be at the beginning of a line.
+ * If so, '^' rules will be active on the next match, otherwise
+ * not.
+ */
+ int yy_at_bol;
+
+ int yy_bs_lineno; /**< The line count. */
+ int yy_bs_column; /**< The column count. */
+
+ /* Whether to try to fill the input buffer when we reach the
+ * end of it.
+ */
+ int yy_fill_buffer;
+
+ int yy_buffer_status;
+
+#define YY_BUFFER_NEW 0
+#define YY_BUFFER_NORMAL 1
+ /* When an EOF's been seen but there's still some text to process
+ * then we mark the buffer as YY_EOF_PENDING, to indicate that we
+ * shouldn't try reading from the input source any more. We might
+ * still have a bunch of tokens to match, though, because of
+ * possible backing-up.
+ *
+ * When we actually see the EOF, we change the status to "new"
+ * (via yyrestart()), so that the user can continue scanning by
+ * just pointing yyin at a new input file.
+ */
+#define YY_BUFFER_EOF_PENDING 2
+
+ };
+#endif /* !YY_STRUCT_YY_BUFFER_STATE */
+
+/* %if-c-only Standard (non-C++) definition */
+/* %not-for-header */
+/* %if-not-reentrant */
+
+/* Stack of input buffers. */
+static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */
+static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */
+static YY_BUFFER_STATE * yy_buffer_stack = NULL; /**< Stack as an array. */
+/* %endif */
+/* %ok-for-header */
+
+/* %endif */
+
+/* We provide macros for accessing buffer states in case in the
+ * future we want to put the buffer states in a more general
+ * "scanner state".
+ *
+ * Returns the top of the stack, or NULL.
+ */
+#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \
+ ? (yy_buffer_stack)[(yy_buffer_stack_top)] \
+ : NULL)
+/* Same as previous macro, but useful when we know that the buffer stack is not
+ * NULL or when we need an lvalue. For internal use only.
+ */
+#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)]
+
+/* %if-c-only Standard (non-C++) definition */
+
+/* %if-not-reentrant */
+/* %not-for-header */
+/* yy_hold_char holds the character lost when yytext is formed. */
+static char yy_hold_char;
+static int yy_n_chars; /* number of characters read into yy_ch_buf */
+int yyleng;
+
+/* Points to current character in buffer. */
+static char *yy_c_buf_p = NULL;
+static int yy_init = 0; /* whether we need to initialize */
+static int yy_start = 0; /* start state number */
+
+/* Flag which is used to allow yywrap()'s to do buffer switches
+ * instead of setting up a fresh yyin. A bit of a hack ...
+ */
+static int yy_did_buffer_switch_on_eof;
+/* %ok-for-header */
+
+/* %endif */
+
+void yyrestart ( FILE *input_file );
+void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer );
+YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size );
+void yy_delete_buffer ( YY_BUFFER_STATE b );
+void yy_flush_buffer ( YY_BUFFER_STATE b );
+void yypush_buffer_state ( YY_BUFFER_STATE new_buffer );
+void yypop_buffer_state ( void );
+
+static void yyensure_buffer_stack ( void );
+static void yy_load_buffer_state ( void );
+static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file );
+#define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER )
+
+YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size );
+YY_BUFFER_STATE yy_scan_string ( const char *yy_str );
+YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len );
+
+/* %endif */
+
+void *yyalloc ( yy_size_t );
+void *yyrealloc ( void *, yy_size_t );
+void yyfree ( void * );
+
+#define yy_new_buffer yy_create_buffer
+#define yy_set_interactive(is_interactive) \
+ { \
+ if ( ! YY_CURRENT_BUFFER ){ \
+ yyensure_buffer_stack (); \
+ YY_CURRENT_BUFFER_LVALUE = \
+ yy_create_buffer( yyin, YY_BUF_SIZE ); \
+ } \
+ YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \
+ }
+#define yy_set_bol(at_bol) \
+ { \
+ if ( ! YY_CURRENT_BUFFER ){\
+ yyensure_buffer_stack (); \
+ YY_CURRENT_BUFFER_LVALUE = \
+ yy_create_buffer( yyin, YY_BUF_SIZE ); \
+ } \
+ YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \
+ }
+#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol)
+
+/* %% [1.0] yytext/yyin/yyout/yy_state_type/yylineno etc. def's & init go here */
+/* Begin user sect3 */
+
+#define parser4_wrap() (/*CONSTCOND*/1)
+#define YY_SKIP_YYWRAP
+
+#define FLEX_DEBUG
+typedef flex_uint8_t YY_CHAR;
+
+FILE *yyin = NULL, *yyout = NULL;
+
+typedef int yy_state_type;
+
+extern int yylineno;
+int yylineno = 1;
+
+extern char *yytext;
+#ifdef yytext_ptr
+#undef yytext_ptr
+#endif
+#define yytext_ptr yytext
+
+/* %% [1.5] DFA */
+
+/* %if-c-only Standard (non-C++) definition */
+
+static yy_state_type yy_get_previous_state ( void );
+static yy_state_type yy_try_NUL_trans ( yy_state_type current_state );
+static int yy_get_next_buffer ( void );
+static void yynoreturn yy_fatal_error ( const char* msg );
+
+/* %endif */
+
+/* Done after the current pattern has been matched and before the
+ * corresponding action - sets up yytext.
+ */
+#define YY_DO_BEFORE_ACTION \
+ (yytext_ptr) = yy_bp; \
+/* %% [2.0] code to fiddle yytext and yyleng for yymore() goes here \ */\
+ yyleng = (int) (yy_cp - yy_bp); \
+ (yy_hold_char) = *yy_cp; \
+ *yy_cp = '\0'; \
+/* %% [3.0] code to copy yytext_ptr to yytext[] goes here, if %array \ */\
+ (yy_c_buf_p) = yy_cp;
+/* %% [4.0] data tables for the DFA and the user's section 1 definitions go here */
+#define YY_NUM_RULES 214
+#define YY_END_OF_BUFFER 215
+/* This struct is not used in this scanner,
+ but its presence is necessary. */
+struct yy_trans_info
+ {
+ flex_int32_t yy_verify;
+ flex_int32_t yy_nxt;
+ };
+static const flex_int16_t yy_accept[2061] =
+ { 0,
+ 207, 207, 0, 0, 0, 0, 0, 0, 0, 0,
+ 215, 213, 10, 11, 213, 1, 207, 204, 207, 207,
+ 213, 206, 205, 213, 213, 213, 213, 213, 200, 201,
+ 213, 213, 213, 202, 203, 5, 5, 5, 213, 213,
+ 213, 10, 11, 0, 0, 195, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 207, 207,
+ 0, 206, 207, 3, 2, 6, 0, 207, 0, 0,
+ 0, 0, 0, 0, 4, 0, 0, 9, 0, 196,
+
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 198, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 2, 0, 0, 0, 0, 0, 0,
+ 0, 8, 0, 0, 0, 0, 0, 0, 0, 0,
+
+ 0, 0, 0, 197, 199, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 88, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+ 0, 0, 0, 0, 0, 0, 0, 0, 212, 210,
+ 0, 209, 208, 0, 0, 0, 0, 0, 0, 0,
+ 173, 0, 172, 0, 0, 94, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 91, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+ 0, 0, 0, 17, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 18, 0, 0, 0, 0, 211, 208, 0,
+ 0, 0, 0, 0, 0, 174, 0, 176, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 98, 0, 0, 0, 0, 0, 0, 80, 0,
+ 0, 0, 0, 0, 0, 120, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 40, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 79, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 83, 0,
+ 41, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 117, 0, 0, 34, 0, 0, 38, 0, 0, 0,
+ 0, 0, 0, 12, 178, 177, 0, 0, 0, 130,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 109, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 36, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 82, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 131, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 126, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 7, 0, 0,
+
+ 179, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 93, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 111, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 107, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 86, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 85, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 124, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 136, 105, 0, 0, 0, 0, 0,
+ 0, 110, 35, 0, 0, 0, 0, 0, 46, 0,
+
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 112,
+ 42, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 74, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 155, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 90, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 125,
+
+ 0, 0, 0, 0, 0, 0, 54, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 39, 0, 0, 0, 0, 33, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 113, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 53, 0, 0, 0, 122, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+ 0, 0, 156, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 87, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 23, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 161, 0, 0, 0, 159,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 183, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 123, 0,
+
+ 0, 0, 0, 0, 0, 0, 127, 0, 0, 0,
+ 0, 0, 0, 0, 108, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 121, 22, 0, 132, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 165, 0, 0, 0, 0, 77, 0, 0, 0,
+ 0, 0, 0, 135, 0, 37, 0, 154, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 62, 0, 0, 0, 0, 0, 0, 0,
+ 101, 102, 0, 0, 0, 0, 0, 0, 0, 0,
+
+ 0, 81, 0, 0, 0, 0, 0, 55, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 129, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 187, 0, 78, 92,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 59, 0, 0, 0, 0, 0, 0, 162, 0,
+ 0, 160, 0, 0, 151, 150, 0, 0, 0, 0,
+ 0, 0, 21, 0, 0, 0, 0, 175, 0, 0,
+
+ 0, 0, 0, 116, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 144, 0, 0, 0, 153, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 133, 0, 15, 0,
+ 0, 43, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 164, 0, 0, 0, 0, 0, 0, 0, 0,
+ 60, 0, 0, 128, 0, 0, 0, 119, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 52, 0, 84, 0, 181, 0, 0, 186, 0, 104,
+ 0, 0, 0, 193, 0, 0, 0, 0, 0, 0,
+
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 14, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 189, 0, 0, 114,
+ 30, 0, 0, 0, 149, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 184, 0, 157,
+ 0, 0, 0, 0, 0, 0, 0, 0, 28, 0,
+
+ 0, 0, 27, 0, 0, 163, 0, 0, 0, 51,
+ 0, 0, 0, 0, 0, 106, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 56, 0, 0, 103, 0, 0, 0, 44, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 134, 0, 0, 0,
+ 29, 0, 0, 0, 185, 0, 0, 0, 0, 0,
+ 145, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 20, 0, 0,
+
+ 188, 0, 76, 0, 47, 0, 0, 0, 182, 180,
+ 0, 31, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 16, 0, 0, 0, 0, 169, 0,
+ 0, 0, 0, 0, 0, 0, 0, 142, 0, 0,
+ 0, 118, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 89, 0, 0, 0, 0, 0, 48, 0,
+ 0, 0, 0, 147, 0, 0, 0, 0, 0, 0,
+ 0, 0, 66, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 170, 13, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 148, 0, 158, 0, 0,
+
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 152, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 141, 0, 58, 57, 19, 0, 166, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 100, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 140,
+ 0, 0, 0, 0, 50, 0, 63, 0, 45, 168,
+ 0, 95, 0, 0, 0, 0, 0, 0, 0, 0,
+ 75, 0, 0, 0, 0, 0, 0, 0, 72, 0,
+
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 49, 0, 0, 0, 0,
+ 0, 0, 70, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 32, 0,
+ 0, 71, 0, 0, 0, 0, 146, 0, 0, 0,
+ 0, 191, 194, 167, 0, 115, 96, 0, 0, 0,
+ 0, 0, 0, 0, 0, 67, 0, 0, 0, 0,
+ 0, 0, 0, 0, 138, 0, 0, 0, 0, 0,
+ 0, 143, 0, 69, 0, 61, 0, 0, 0, 0,
+
+ 0, 0, 97, 0, 0, 0, 0, 0, 0, 0,
+ 137, 0, 0, 171, 192, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 68, 0, 0, 0, 0, 26, 24, 0, 0, 0,
+ 73, 0, 0, 99, 0, 65, 0, 0, 0, 0,
+ 0, 0, 0, 0, 64, 0, 139, 25, 190, 0
+ } ;
+
+static const YY_CHAR yy_ec[256] =
+ { 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 2, 3,
+ 1, 1, 2, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 4, 5, 6, 7, 5, 5, 5, 5, 5,
+ 5, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 14, 17, 14, 18, 14, 14, 14, 19, 5, 20,
+ 5, 21, 22, 5, 23, 24, 25, 26, 27, 28,
+ 5, 29, 5, 30, 5, 31, 5, 32, 33, 34,
+ 5, 35, 36, 37, 38, 39, 40, 5, 41, 5,
+ 42, 43, 44, 5, 45, 5, 46, 47, 48, 49,
+
+ 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
+ 60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
+ 70, 71, 72, 5, 73, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5
+ } ;
+
+static const YY_CHAR yy_meta[74] =
+ { 0,
+ 1, 1, 2, 1, 1, 3, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1
+ } ;
+
+static const flex_int16_t yy_base[2069] =
+ { 0,
+ 0, 72, 21, 31, 43, 51, 54, 60, 91, 99,
+ 2447, 2448, 34, 2443, 145, 0, 207, 2448, 214, 221,
+ 13, 228, 2448, 2423, 118, 25, 2, 6, 2448, 2448,
+ 73, 11, 17, 2448, 2448, 2448, 104, 2431, 2384, 0,
+ 2421, 108, 2438, 24, 262, 2448, 2380, 67, 2386, 84,
+ 75, 88, 224, 91, 89, 290, 220, 2378, 206, 285,
+ 236, 204, 197, 60, 232, 2387, 247, 294, 315, 307,
+ 286, 2370, 212, 325, 354, 328, 2389, 0, 366, 382,
+ 397, 411, 404, 2448, 0, 2448, 419, 425, 249, 319,
+ 238, 331, 399, 329, 2448, 2386, 2427, 2448, 404, 2448,
+
+ 438, 2414, 405, 2383, 415, 10, 409, 311, 411, 418,
+ 428, 2424, 0, 499, 418, 2366, 2363, 2367, 402, 2363,
+ 80, 2371, 216, 2357, 2358, 2363, 78, 2373, 2356, 2365,
+ 2355, 2364, 221, 2355, 223, 2356, 2354, 400, 2402, 2406,
+ 2346, 2399, 2339, 174, 2360, 2360, 2354, 278, 2347, 2345,
+ 2346, 2338, 2343, 2337, 423, 2348, 346, 2333, 2332, 2346,
+ 405, 2332, 420, 355, 2326, 495, 407, 428, 2347, 2344,
+ 2345, 428, 2343, 2378, 2377, 2323, 2323, 433, 2324, 433,
+ 2316, 2333, 2325, 0, 446, 462, 471, 464, 476, 481,
+ 2324, 2448, 2369, 486, 2318, 466, 479, 485, 2372, 510,
+
+ 2371, 502, 2370, 2448, 2448, 548, 2369, 521, 2328, 2320,
+ 2307, 2318, 2322, 2323, 2303, 2314, 2318, 2315, 2314, 478,
+ 518, 2352, 2316, 2297, 2294, 2302, 2297, 2311, 2307, 2298,
+ 2294, 2306, 2306, 2297, 2281, 2285, 2298, 2300, 2297, 2289,
+ 2279, 2297, 2448, 2292, 505, 2330, 2276, 2285, 2327, 2274,
+ 2284, 2287, 518, 2283, 2322, 2267, 2269, 2280, 2318, 2263,
+ 2321, 2275, 2255, 2270, 542, 2260, 2266, 524, 2257, 2255,
+ 2255, 2261, 2252, 2251, 2258, 2248, 2307, 2263, 2262, 2256,
+ 498, 2263, 2258, 2250, 2240, 2255, 2254, 2249, 2253, 2234,
+ 2250, 2236, 2242, 2249, 2231, 457, 2236, 2233, 2232, 2227,
+
+ 2241, 551, 2240, 2283, 2242, 529, 2233, 552, 2448, 2448,
+ 554, 2448, 2448, 2220, 536, 543, 2268, 559, 2278, 553,
+ 2448, 2277, 2448, 2271, 603, 2448, 552, 2211, 2220, 2268,
+ 2228, 2211, 2228, 2264, 2224, 2207, 2213, 2265, 2220, 2223,
+ 2214, 2217, 2203, 2214, 2258, 2252, 2210, 2207, 594, 2213,
+ 2253, 2188, 2246, 2199, 2194, 2191, 2242, 2199, 2188, 2204,
+ 2238, 2184, 601, 2198, 2183, 2196, 2194, 2192, 2192, 2191,
+ 2186, 2193, 2188, 2184, 570, 2182, 2185, 2180, 2166, 2178,
+ 2174, 2224, 563, 2218, 2448, 2182, 2216, 2166, 2165, 2164,
+ 2157, 2159, 2171, 2162, 2169, 2150, 2167, 2162, 581, 2209,
+
+ 2162, 2159, 2162, 2448, 2161, 2150, 2150, 2162, 547, 2137,
+ 2138, 2159, 2150, 2140, 2191, 2136, 2150, 584, 2136, 2148,
+ 2147, 2146, 2141, 2183, 2143, 2142, 2141, 2140, 2183, 2142,
+ 2122, 2180, 2448, 2122, 2121, 629, 2134, 2448, 2448, 2133,
+ 2122, 2114, 572, 2173, 2172, 2448, 2171, 2448, 607, 655,
+ 610, 2170, 2112, 2123, 2162, 2115, 2117, 2119, 2106, 2114,
+ 2102, 2448, 2107, 2100, 2112, 2115, 2102, 2101, 2448, 595,
+ 2103, 2100, 613, 2098, 2100, 2448, 2145, 2107, 2104, 2089,
+ 2102, 2097, 637, 2104, 2092, 2085, 2136, 2448, 2083, 2099,
+ 2133, 2094, 2091, 2092, 2076, 2085, 2127, 2078, 2077, 2072,
+
+ 2071, 2122, 2066, 626, 2085, 2059, 2066, 2071, 2081, 2115,
+ 2119, 2448, 2064, 2060, 2058, 2067, 2066, 2060, 2067, 2051,
+ 2051, 2061, 2049, 2063, 2063, 2051, 2047, 2045, 2448, 2102,
+ 2448, 2044, 2055, 2094, 2039, 2044, 2053, 2047, 2041, 2050,
+ 2092, 2086, 2048, 2032, 2027, 2047, 2022, 2028, 631, 2042,
+ 2035, 2039, 2022, 2080, 2021, 2021, 2072, 2017, 2018, 2017,
+ 2448, 2030, 2067, 2448, 2018, 2016, 2448, 2027, 2063, 2023,
+ 2007, 2024, 2064, 2448, 2448, 2448, 630, 621, 681, 2448,
+ 2015, 2014, 294, 2021, 2001, 2011, 2053, 1998, 2051, 1996,
+ 2006, 2048, 1993, 2000, 1993, 2005, 1987, 1987, 2002, 2001,
+
+ 603, 2000, 1999, 1999, 1981, 1986, 2027, 1994, 1991, 1985,
+ 2030, 1974, 1990, 1989, 2448, 1974, 1971, 2029, 1984, 1976,
+ 1982, 1973, 1981, 1966, 1982, 1964, 1976, 1968, 640, 1959,
+ 1958, 1952, 1957, 1972, 1969, 1970, 1949, 1959, 1965, 2008,
+ 1963, 1955, 1946, 2448, 1947, 1949, 1958, 1950, 1955, 1994,
+ 1993, 1944, 16, 1953, 1990, 1935, 1988, 1935, 1938, 1931,
+ 2448, 1945, 1924, 1928, 1942, 1934, 1979, 1931, 1938, 1976,
+ 2448, 1921, 1935, 1934, 1937, 1918, 1970, 1969, 1916, 1967,
+ 1927, 1911, 1964, 1963, 2448, 1908, 1922, 1921, 679, 1922,
+ 1921, 1957, 1921, 1916, 1899, 1904, 1906, 2448, 1912, 1902,
+
+ 2448, 654, 666, 1896, 1894, 1901, 1911, 1903, 1890, 1884,
+ 1896, 1895, 1940, 659, 1944, 1898, 649, 20, 242, 210,
+ 437, 541, 577, 564, 585, 646, 649, 641, 698, 651,
+ 661, 664, 703, 666, 660, 664, 662, 664, 711, 717,
+ 675, 676, 2448, 679, 675, 666, 681, 686, 683, 686,
+ 685, 673, 687, 685, 693, 729, 691, 736, 737, 687,
+ 679, 689, 736, 694, 744, 2448, 745, 698, 700, 696,
+ 690, 693, 751, 747, 710, 699, 712, 2448, 702, 713,
+ 702, 715, 705, 718, 762, 763, 706, 717, 702, 725,
+ 704, 710, 765, 729, 713, 725, 769, 770, 717, 772,
+
+ 736, 731, 736, 734, 778, 720, 732, 735, 739, 731,
+ 728, 2448, 792, 751, 752, 742, 745, 756, 741, 748,
+ 754, 746, 760, 800, 776, 763, 768, 765, 747, 754,
+ 768, 813, 770, 767, 768, 766, 775, 2448, 771, 766,
+ 781, 778, 764, 766, 785, 782, 770, 777, 778, 787,
+ 791, 827, 789, 780, 778, 777, 788, 784, 840, 787,
+ 798, 783, 784, 790, 806, 796, 2448, 806, 806, 799,
+ 810, 808, 853, 795, 797, 812, 799, 798, 860, 817,
+ 803, 809, 807, 2448, 2448, 817, 822, 827, 815, 825,
+ 827, 2448, 2448, 828, 815, 833, 820, 814, 2448, 819,
+
+ 837, 824, 874, 825, 877, 823, 827, 845, 886, 2448,
+ 2448, 831, 835, 834, 831, 891, 844, 834, 835, 831,
+ 844, 855, 856, 851, 852, 854, 847, 849, 845, 851,
+ 851, 853, 868, 851, 910, 867, 872, 849, 872, 858,
+ 857, 2448, 864, 865, 865, 878, 916, 874, 864, 879,
+ 880, 867, 899, 907, 876, 871, 926, 927, 889, 929,
+ 2448, 935, 878, 894, 898, 939, 889, 888, 883, 884,
+ 896, 891, 887, 890, 891, 901, 910, 947, 894, 913,
+ 905, 900, 958, 915, 916, 907, 2448, 921, 910, 913,
+ 921, 923, 908, 924, 918, 965, 931, 915, 916, 2448,
+
+ 932, 935, 918, 977, 920, 939, 2448, 938, 941, 927,
+ 922, 940, 980, 938, 934, 931, 984, 985, 947, 933,
+ 951, 950, 951, 937, 952, 944, 951, 941, 959, 958,
+ 945, 962, 2448, 954, 960, 963, 1008, 2448, 957, 962,
+ 1006, 958, 970, 964, 965, 963, 965, 975, 1020, 964,
+ 965, 965, 1024, 968, 980, 973, 2448, 969, 977, 975,
+ 1026, 968, 989, 975, 977, 983, 991, 982, 987, 997,
+ 998, 1003, 1044, 1016, 1021, 1003, 1000, 996, 1010, 993,
+ 993, 2448, 994, 1054, 997, 2448, 1008, 998, 1018, 1017,
+ 1008, 1019, 1062, 1023, 1016, 1020, 1024, 1007, 1024, 1015,
+
+ 1070, 1017, 2448, 1067, 1016, 1019, 1037, 1019, 1020, 1020,
+ 1041, 1038, 1043, 1044, 1030, 1038, 1047, 1027, 1042, 1049,
+ 1091, 2448, 1092, 1093, 1035, 1045, 1055, 1039, 1059, 1047,
+ 1043, 1050, 1059, 1047, 1054, 1055, 1067, 1108, 1055, 1053,
+ 1055, 1072, 1113, 1063, 1062, 1068, 1066, 1064, 1059, 1120,
+ 1071, 1122, 1118, 1124, 2448, 1081, 1074, 1065, 1084, 1072,
+ 1082, 1083, 1079, 1092, 1092, 2448, 1076, 1072, 1079, 2448,
+ 1080, 1140, 1099, 1080, 1099, 1100, 1102, 1141, 1094, 1148,
+ 1149, 1099, 1097, 1108, 1107, 1091, 1096, 1114, 2448, 1136,
+ 1127, 1159, 1099, 1121, 1116, 1116, 1120, 1165, 2448, 1108,
+
+ 1108, 1111, 1128, 1123, 1127, 1122, 2448, 1114, 1131, 1111,
+ 1132, 1122, 1173, 1126, 2448, 1140, 1140, 1178, 1125, 1134,
+ 1182, 1140, 1145, 1131, 1191, 1134, 1145, 1137, 1143, 1139,
+ 1157, 1158, 1159, 2448, 2448, 1158, 2448, 1143, 1154, 1145,
+ 1164, 1157, 1155, 1148, 1160, 1204, 1168, 1157, 1164, 1165,
+ 1171, 2448, 1164, 1216, 1157, 1218, 2448, 1219, 1161, 1167,
+ 1174, 1218, 1180, 2448, 1181, 2448, 1168, 2448, 1170, 1184,
+ 1189, 1172, 1231, 1188, 1189, 1229, 1181, 1186, 1237, 1233,
+ 1186, 1240, 2448, 1191, 1188, 1243, 1244, 1201, 1202, 1204,
+ 2448, 2448, 1243, 1192, 1208, 1251, 1193, 1205, 1210, 1225,
+
+ 1256, 2448, 1213, 1206, 1215, 1206, 1217, 2448, 1262, 1199,
+ 1210, 1225, 1261, 1213, 1224, 1225, 1226, 1223, 1267, 1230,
+ 1220, 1230, 1236, 1223, 1219, 1279, 1275, 1233, 1277, 1284,
+ 1241, 2448, 1242, 1236, 1245, 1246, 1243, 1233, 1236, 1236,
+ 1241, 1296, 1243, 1298, 1241, 1246, 1301, 1297, 1238, 1253,
+ 1246, 1250, 1263, 1264, 1262, 1311, 2448, 1265, 2448, 2448,
+ 1270, 1262, 1272, 1257, 1313, 1260, 1260, 1321, 1265, 1275,
+ 1324, 2448, 1272, 1273, 1275, 1328, 1269, 1272, 2448, 1290,
+ 1291, 2448, 1291, 1279, 2448, 2448, 1292, 1275, 1295, 1282,
+ 1341, 1291, 2448, 1343, 1292, 1345, 1325, 2448, 1347, 1304,
+
+ 1349, 1300, 1347, 2448, 1295, 1354, 1304, 1298, 1295, 1298,
+ 1300, 1317, 1356, 1309, 1321, 1324, 1312, 1315, 1308, 1315,
+ 1306, 2448, 1314, 1329, 1314, 2448, 1316, 1317, 1332, 1332,
+ 1335, 1335, 1332, 1376, 1338, 1330, 2448, 1331, 2448, 1341,
+ 1333, 2448, 1339, 1344, 1345, 1342, 1386, 1334, 1349, 1350,
+ 1342, 2448, 1339, 1340, 1340, 1346, 1345, 1343, 1397, 1358,
+ 2448, 1399, 1346, 2448, 1347, 1348, 1354, 2448, 1363, 1357,
+ 1412, 1359, 1352, 1364, 1359, 1369, 1380, 1373, 1379, 1375,
+ 2448, 1384, 2448, 1381, 2448, 1378, 1401, 2448, 1428, 2448,
+ 1386, 1371, 1431, 2448, 1432, 1389, 1394, 1376, 1436, 1432,
+
+ 1396, 1393, 1389, 1382, 1437, 1395, 1396, 1386, 1391, 1403,
+ 1448, 1403, 1451, 1447, 1410, 1407, 1451, 1399, 1404, 1402,
+ 1461, 1417, 1405, 1464, 1460, 1423, 1467, 1428, 1417, 1411,
+ 1471, 1413, 1414, 1428, 1431, 1431, 1477, 1419, 1436, 1432,
+ 1430, 1437, 1421, 1479, 1480, 1481, 1444, 1437, 1436, 1436,
+ 1431, 1449, 2448, 1440, 1446, 1451, 1438, 1439, 1438, 1440,
+ 1495, 1436, 1454, 1503, 1461, 1474, 2448, 1506, 1459, 2448,
+ 2448, 1450, 1461, 1510, 2448, 1456, 1461, 1459, 1509, 1467,
+ 1472, 1460, 1476, 1466, 1464, 1478, 1465, 2448, 1467, 2448,
+ 1470, 1521, 1464, 1469, 1529, 1477, 1487, 1488, 2448, 1534,
+
+ 1487, 1531, 2448, 1482, 1538, 2448, 1481, 1496, 1484, 2448,
+ 1494, 1495, 1544, 1502, 1506, 2448, 1542, 1508, 1484, 1502,
+ 1503, 1512, 1494, 1501, 1513, 1512, 1503, 1553, 1505, 1501,
+ 1561, 1503, 1504, 1564, 1560, 1566, 1516, 1568, 1521, 1526,
+ 1519, 2448, 1572, 1573, 2448, 1522, 1575, 1517, 2448, 1520,
+ 1530, 1574, 1522, 1537, 1524, 1539, 1525, 1532, 1532, 1587,
+ 1544, 1545, 1533, 1532, 1592, 1545, 2448, 1589, 1540, 1537,
+ 2448, 1551, 1538, 1559, 2448, 1556, 1601, 1554, 1551, 1552,
+ 2448, 1605, 1564, 1547, 1549, 1565, 1558, 1548, 1564, 1565,
+ 1574, 1564, 1616, 1576, 1560, 1569, 1615, 2448, 1573, 1622,
+
+ 2448, 1571, 2448, 1565, 2448, 1572, 1626, 1622, 2448, 2448,
+ 1585, 2448, 1625, 1587, 1588, 1573, 1574, 1582, 1636, 1584,
+ 1633, 1634, 1589, 2448, 1584, 1602, 1589, 1596, 2448, 1593,
+ 1598, 1596, 1648, 1649, 1597, 1594, 1594, 2448, 1609, 1610,
+ 1611, 2448, 1612, 1602, 1614, 1659, 1612, 1661, 1603, 1611,
+ 1612, 1625, 2448, 1626, 1607, 1608, 1615, 1617, 2448, 1631,
+ 1628, 1668, 1629, 2448, 1633, 1618, 1618, 1633, 1626, 1625,
+ 1636, 1623, 2448, 1639, 1627, 1630, 1686, 1626, 1645, 1647,
+ 1638, 1647, 1649, 1653, 2448, 2448, 1646, 1637, 1696, 1639,
+ 1698, 1699, 1700, 1658, 1702, 2448, 1644, 2448, 1699, 1661,
+
+ 1662, 1649, 1657, 1668, 1655, 1666, 1652, 1650, 1658, 1657,
+ 1662, 1654, 1719, 1672, 1673, 1663, 1680, 1679, 1720, 1664,
+ 1683, 1684, 2448, 1689, 1682, 1687, 1688, 1733, 1690, 1693,
+ 1680, 1689, 2448, 1738, 2448, 2448, 2448, 1699, 2448, 1740,
+ 1682, 1742, 1743, 1686, 1745, 1693, 1742, 1743, 1705, 1745,
+ 1691, 1695, 1713, 1754, 1713, 2448, 1704, 1694, 1753, 1719,
+ 1712, 1708, 1762, 1705, 1707, 1706, 1713, 1709, 1711, 2448,
+ 1716, 1726, 1711, 1719, 2448, 1714, 2448, 1726, 2448, 2448,
+ 1731, 2448, 1732, 1732, 1723, 1779, 1723, 1733, 1718, 1731,
+ 2448, 1733, 1741, 1786, 1727, 1729, 1726, 1750, 2448, 1737,
+
+ 1744, 1745, 1748, 1737, 1791, 1738, 1746, 1755, 1754, 1757,
+ 1750, 1745, 1745, 1757, 1752, 2448, 1763, 1751, 1804, 1755,
+ 1767, 1812, 2448, 1758, 1770, 1815, 1764, 1766, 1764, 1765,
+ 1820, 1773, 1760, 1775, 1780, 1825, 1826, 1827, 1784, 1829,
+ 1830, 1774, 1778, 1774, 1790, 1773, 1785, 1779, 2448, 1795,
+ 1839, 2448, 1796, 1781, 1799, 1790, 2448, 1791, 1805, 1801,
+ 1794, 2448, 2448, 2448, 1848, 2448, 2448, 1791, 1799, 1794,
+ 1847, 1813, 1810, 1855, 1816, 2448, 1857, 1799, 1859, 1802,
+ 1815, 1814, 1815, 1805, 2448, 1806, 1866, 1819, 1828, 1821,
+ 1865, 2448, 1812, 2448, 1824, 2448, 1868, 1874, 1816, 1832,
+
+ 1877, 1878, 2448, 1835, 1829, 1822, 1842, 1839, 1831, 1835,
+ 2448, 1882, 1831, 2448, 2448, 1831, 1839, 1886, 1846, 1839,
+ 1894, 1842, 1842, 1838, 1840, 1899, 1900, 1842, 1858, 1843,
+ 2448, 1904, 1851, 1858, 1907, 2448, 2448, 1860, 1909, 1857,
+ 2448, 1861, 1860, 2448, 1861, 2448, 1855, 1855, 1872, 1873,
+ 1918, 1864, 1920, 1921, 2448, 1922, 2448, 2448, 2448, 2448,
+ 1928, 1931, 1934, 1935, 1937, 1940, 1943, 1946
+ } ;
+
+static const flex_int16_t yy_def[2069] =
+ { 0,
+ 2061, 2061, 2062, 2062, 2061, 2061, 2061, 2061, 2061, 2061,
+ 2060, 2060, 2060, 2060, 2060, 2063, 2060, 2060, 2060, 2060,
+ 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060,
+ 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2064,
+ 2060, 2060, 2060, 2065, 15, 2060, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 2066, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 2063, 2060, 2060,
+ 2060, 2060, 2060, 2060, 2067, 2060, 2060, 2060, 2060, 2060,
+ 2060, 2060, 2060, 2060, 2060, 2060, 2064, 2060, 2065, 2060,
+
+ 2060, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 2068, 45, 2066, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 2067, 2060, 2060, 2060, 2060, 2060, 2060,
+ 2060, 2060, 45, 45, 45, 45, 45, 45, 45, 45,
+
+ 45, 45, 2068, 2060, 2060, 114, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 2060, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+
+ 45, 45, 45, 45, 45, 45, 45, 2060, 2060, 2060,
+ 2060, 2060, 2060, 2060, 45, 45, 45, 45, 45, 45,
+ 2060, 45, 2060, 45, 114, 2060, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 2060, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+
+ 45, 45, 45, 2060, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 2060, 45, 45, 45, 45, 2060, 2060, 2060,
+ 45, 45, 45, 45, 45, 2060, 45, 2060, 45, 114,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 2060, 45, 45, 45, 45, 45, 45, 2060, 45,
+ 45, 45, 45, 45, 45, 2060, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 2060, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 2060, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 2060, 45,
+ 2060, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 2060, 45, 45, 2060, 45, 45, 2060, 45, 45, 2060,
+ 45, 45, 45, 2060, 2060, 2060, 45, 45, 45, 2060,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 2060, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 2060, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 2060, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 2060, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 2060, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 2060, 45, 45,
+
+ 2060, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 2060, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 2060, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 2060, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 2060, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 2060, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 2060, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 2060, 2060, 45, 45, 45, 45, 45,
+ 45, 2060, 2060, 45, 45, 45, 45, 45, 2060, 45,
+
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 2060,
+ 2060, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 2060, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 2060, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 2060, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 2060,
+
+ 45, 45, 45, 45, 45, 45, 2060, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 2060, 45, 45, 45, 45, 2060, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 2060, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 2060, 45, 45, 45, 2060, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+
+ 45, 45, 2060, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 2060, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 2060, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 2060, 45, 45, 45, 2060,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 2060, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 2060, 45,
+
+ 45, 45, 45, 45, 45, 45, 2060, 45, 45, 45,
+ 45, 45, 45, 45, 2060, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 2060, 2060, 45, 2060, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 2060, 45, 45, 45, 45, 2060, 45, 45, 45,
+ 45, 45, 45, 2060, 45, 2060, 45, 2060, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 2060, 45, 45, 45, 45, 45, 45, 45,
+ 2060, 2060, 45, 45, 45, 45, 45, 45, 45, 45,
+
+ 45, 2060, 45, 45, 45, 45, 45, 2060, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 2060, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 2060, 45, 2060, 2060,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 2060, 45, 45, 45, 45, 45, 45, 2060, 45,
+ 45, 2060, 45, 45, 2060, 2060, 45, 45, 45, 45,
+ 45, 45, 2060, 45, 45, 45, 45, 2060, 45, 45,
+
+ 45, 45, 45, 2060, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 2060, 45, 45, 45, 2060, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 2060, 45, 2060, 45,
+ 45, 2060, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 2060, 45, 45, 45, 45, 45, 45, 45, 45,
+ 2060, 45, 45, 2060, 45, 45, 45, 2060, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 2060, 45, 2060, 45, 2060, 45, 45, 2060, 45, 2060,
+ 45, 45, 45, 2060, 45, 45, 45, 45, 45, 45,
+
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 2060, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 2060, 45, 45, 2060,
+ 2060, 45, 45, 45, 2060, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 2060, 45, 2060,
+ 45, 45, 45, 45, 45, 45, 45, 45, 2060, 45,
+
+ 45, 45, 2060, 45, 45, 2060, 45, 45, 45, 2060,
+ 45, 45, 45, 45, 45, 2060, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 2060, 45, 45, 2060, 45, 45, 45, 2060, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 2060, 45, 45, 45,
+ 2060, 45, 45, 45, 2060, 45, 45, 45, 45, 45,
+ 2060, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 2060, 45, 45,
+
+ 2060, 45, 2060, 45, 2060, 45, 45, 45, 2060, 2060,
+ 45, 2060, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 2060, 45, 45, 45, 45, 2060, 45,
+ 45, 45, 45, 45, 45, 45, 45, 2060, 45, 45,
+ 45, 2060, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 2060, 45, 45, 45, 45, 45, 2060, 45,
+ 45, 45, 45, 2060, 45, 45, 45, 45, 45, 45,
+ 45, 45, 2060, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 2060, 2060, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 2060, 45, 2060, 45, 45,
+
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 2060, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 2060, 45, 2060, 2060, 2060, 45, 2060, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 2060, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 2060,
+ 45, 45, 45, 45, 2060, 45, 2060, 45, 2060, 2060,
+ 45, 2060, 45, 45, 45, 45, 45, 45, 45, 45,
+ 2060, 45, 45, 45, 45, 45, 45, 45, 2060, 45,
+
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 2060, 45, 45, 45, 45,
+ 45, 45, 2060, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 2060, 45,
+ 45, 2060, 45, 45, 45, 45, 2060, 45, 45, 45,
+ 45, 2060, 2060, 2060, 45, 2060, 2060, 45, 45, 45,
+ 45, 45, 45, 45, 45, 2060, 45, 45, 45, 45,
+ 45, 45, 45, 45, 2060, 45, 45, 45, 45, 45,
+ 45, 2060, 45, 2060, 45, 2060, 45, 45, 45, 45,
+
+ 45, 45, 2060, 45, 45, 45, 45, 45, 45, 45,
+ 2060, 45, 45, 2060, 2060, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 2060, 45, 45, 45, 45, 2060, 2060, 45, 45, 45,
+ 2060, 45, 45, 2060, 45, 2060, 45, 45, 45, 45,
+ 45, 45, 45, 45, 2060, 45, 2060, 2060, 2060, 0,
+ 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060
+ } ;
+
+static const flex_int16_t yy_nxt[2522] =
+ { 0,
+ 2060, 13, 14, 13, 2060, 15, 16, 2060, 17, 18,
+ 19, 20, 21, 22, 22, 22, 22, 22, 23, 24,
+ 84, 778, 37, 14, 37, 85, 25, 26, 38, 100,
+ 843, 27, 37, 14, 37, 42, 28, 42, 38, 90,
+ 91, 29, 197, 30, 13, 14, 13, 89, 90, 25,
+ 31, 91, 13, 14, 13, 13, 14, 13, 32, 40,
+ 779, 13, 14, 13, 33, 40, 101, 90, 91, 197,
+ 89, 34, 35, 13, 14, 13, 93, 15, 16, 94,
+ 17, 18, 19, 20, 21, 22, 22, 22, 22, 22,
+ 23, 24, 13, 14, 13, 89, 39, 103, 25, 26,
+
+ 13, 14, 13, 27, 39, 42, 105, 42, 28, 42,
+ 106, 42, 41, 29, 107, 30, 110, 111, 92, 138,
+ 41, 25, 31, 103, 225, 226, 87, 139, 87, 105,
+ 32, 88, 88, 88, 88, 88, 33, 107, 106, 110,
+ 216, 111, 217, 34, 35, 44, 44, 44, 45, 45,
+ 46, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 47, 45, 45, 45, 45, 45, 48, 45, 45,
+ 49, 45, 50, 45, 51, 45, 52, 45, 45, 45,
+ 45, 53, 54, 45, 55, 45, 45, 56, 45, 45,
+ 57, 58, 59, 60, 61, 62, 63, 64, 65, 51,
+
+ 66, 67, 68, 69, 70, 71, 72, 73, 74, 75,
+ 76, 77, 55, 45, 45, 45, 45, 45, 79, 247,
+ 80, 80, 80, 80, 80, 79, 105, 82, 82, 82,
+ 82, 82, 248, 81, 83, 83, 83, 83, 83, 79,
+ 81, 82, 82, 82, 82, 82, 136, 81, 108, 105,
+ 103, 119, 844, 137, 81, 120, 81, 165, 109, 121,
+ 135, 166, 122, 81, 219, 123, 232, 845, 233, 124,
+ 81, 108, 235, 220, 221, 187, 115, 81, 45, 185,
+ 140, 131, 116, 132, 45, 117, 109, 45, 236, 45,
+ 141, 45, 142, 45, 133, 113, 144, 145, 45, 45,
+
+ 146, 45, 45, 187, 134, 185, 147, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 125, 161, 45, 126, 127, 162, 45, 128, 129, 148,
+ 45, 107, 252, 149, 199, 163, 253, 150, 45, 186,
+ 130, 707, 45, 110, 45, 114, 262, 156, 708, 151,
+ 153, 185, 154, 152, 155, 157, 187, 158, 174, 175,
+ 167, 199, 159, 160, 168, 186, 180, 169, 108, 83,
+ 83, 83, 83, 83, 170, 171, 181, 188, 109, 172,
+ 173, 182, 81, 79, 190, 80, 80, 80, 80, 80,
+
+ 275, 108, 263, 176, 276, 87, 177, 87, 81, 100,
+ 88, 88, 88, 88, 88, 81, 178, 83, 83, 83,
+ 83, 83, 79, 179, 82, 82, 82, 82, 82, 186,
+ 81, 81, 88, 88, 88, 88, 88, 81, 88, 88,
+ 88, 88, 88, 99, 194, 196, 101, 198, 200, 212,
+ 99, 201, 267, 81, 202, 189, 239, 194, 213, 240,
+ 81, 198, 214, 241, 287, 288, 201, 268, 269, 270,
+ 200, 196, 194, 295, 207, 198, 200, 202, 201, 272,
+ 99, 308, 273, 274, 99, 208, 289, 296, 99, 198,
+ 290, 260, 309, 304, 291, 846, 99, 310, 302, 308,
+
+ 99, 318, 99, 99, 205, 278, 309, 310, 316, 308,
+ 319, 320, 206, 206, 206, 206, 206, 424, 309, 425,
+ 310, 206, 206, 206, 206, 206, 206, 311, 375, 318,
+ 313, 316, 312, 324, 320, 339, 322, 319, 340, 435,
+ 279, 280, 281, 316, 206, 206, 206, 206, 206, 206,
+ 366, 282, 389, 283, 408, 284, 285, 409, 286, 322,
+ 324, 325, 325, 325, 325, 325, 327, 367, 341, 394,
+ 325, 325, 325, 325, 325, 325, 342, 322, 438, 395,
+ 438, 376, 343, 443, 377, 445, 529, 447, 390, 441,
+ 847, 436, 443, 325, 325, 325, 325, 325, 325, 442,
+
+ 322, 438, 391, 439, 473, 539, 488, 573, 445, 540,
+ 474, 489, 443, 513, 431, 447, 450, 450, 450, 450,
+ 450, 451, 848, 514, 849, 450, 450, 450, 450, 450,
+ 450, 503, 504, 549, 567, 573, 505, 550, 577, 568,
+ 578, 679, 615, 850, 530, 573, 596, 616, 450, 450,
+ 450, 450, 450, 450, 597, 703, 598, 599, 600, 490,
+ 601, 604, 702, 726, 491, 577, 727, 578, 45, 45,
+ 45, 45, 45, 579, 605, 636, 606, 45, 45, 45,
+ 45, 45, 45, 703, 812, 637, 701, 754, 755, 702,
+ 824, 704, 825, 680, 851, 813, 840, 841, 852, 842,
+
+ 45, 45, 45, 45, 45, 45, 836, 853, 854, 855,
+ 856, 857, 837, 858, 859, 825, 860, 861, 824, 862,
+ 865, 866, 867, 863, 868, 869, 870, 864, 871, 872,
+ 873, 874, 875, 876, 877, 878, 879, 880, 881, 882,
+ 883, 884, 885, 886, 887, 888, 890, 891, 889, 892,
+ 893, 894, 895, 896, 897, 898, 899, 900, 901, 902,
+ 903, 904, 905, 906, 907, 908, 909, 910, 911, 912,
+ 913, 914, 915, 916, 917, 918, 919, 920, 921, 922,
+ 923, 924, 925, 926, 927, 929, 930, 931, 934, 935,
+ 936, 928, 939, 937, 932, 940, 941, 942, 933, 938,
+
+ 943, 944, 945, 946, 947, 948, 949, 950, 951, 952,
+ 953, 954, 955, 956, 957, 958, 959, 960, 961, 962,
+ 963, 964, 965, 966, 967, 968, 969, 970, 971, 972,
+ 973, 974, 975, 976, 977, 978, 979, 980, 981, 954,
+ 982, 983, 984, 985, 986, 987, 988, 989, 990, 991,
+ 992, 993, 994, 995, 996, 997, 998, 999, 1000, 1002,
+ 1003, 1004, 1005, 1001, 1006, 1007, 1008, 1009, 1010, 1011,
+ 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021,
+ 1022, 1023, 1024, 1026, 1027, 1028, 1025, 1029, 1030, 1031,
+ 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041,
+
+ 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051,
+ 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061,
+ 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071,
+ 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081,
+ 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091,
+ 1092, 1093, 1094, 1095, 1096, 1097, 1075, 1098, 1099, 1074,
+ 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109,
+ 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119,
+ 1120, 1121, 1122, 1124, 1125, 1126, 1127, 1128, 1129, 1130,
+ 1131, 1132, 1133, 1134, 1135, 1136, 1137, 1138, 1139, 1140,
+
+ 1141, 1142, 1143, 1144, 1145, 1146, 1147, 1149, 1150, 1151,
+ 1152, 1153, 1154, 1155, 1156, 1157, 1158, 1148, 1159, 1160,
+ 1161, 1162, 1163, 1164, 1165, 1166, 1167, 1168, 1169, 1170,
+ 1171, 1172, 1173, 1174, 1175, 1176, 1177, 1178, 1179, 1180,
+ 1123, 1181, 1182, 1183, 1184, 1185, 1186, 1187, 1188, 1189,
+ 1190, 1192, 1191, 1193, 1194, 1195, 1196, 1197, 1198, 1199,
+ 1200, 1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209,
+ 1210, 1211, 1212, 1213, 1214, 1215, 1216, 1217, 1190, 1191,
+ 1218, 1219, 1220, 1221, 1222, 1223, 1224, 1225, 1226, 1227,
+ 1228, 1229, 1230, 1231, 1232, 1233, 1234, 1235, 1237, 1239,
+
+ 1240, 1241, 1236, 1242, 1243, 1244, 1245, 1246, 1247, 1248,
+ 1249, 1250, 1251, 1252, 1253, 1254, 1255, 1256, 1257, 1258,
+ 1259, 1260, 1261, 1262, 1263, 1264, 1265, 1266, 1267, 1268,
+ 1269, 1270, 1271, 1272, 1273, 1274, 1275, 1276, 1277, 1278,
+ 1279, 1280, 1238, 1281, 1282, 1283, 1284, 1285, 1286, 1287,
+ 1288, 1289, 1290, 1291, 1292, 1293, 1294, 1295, 1296, 1297,
+ 1298, 1299, 1300, 1301, 1302, 1303, 1304, 1305, 1306, 1307,
+ 1308, 1309, 1310, 1311, 1312, 1313, 1314, 1315, 1316, 1317,
+ 1318, 1319, 1320, 1321, 1322, 1300, 1323, 1324, 1325, 1326,
+ 1327, 1301, 1328, 1329, 1330, 1331, 1332, 1333, 1334, 1335,
+
+ 1336, 1337, 1338, 1339, 1340, 1341, 1342, 1343, 1344, 1345,
+ 1346, 1347, 1348, 1349, 1350, 1351, 1352, 1353, 1354, 1355,
+ 1356, 1357, 1358, 1359, 1360, 1361, 1362, 1363, 1364, 1365,
+ 1366, 1367, 1368, 1369, 1370, 1371, 1372, 1373, 1374, 1375,
+ 1377, 1378, 1379, 1380, 1381, 1382, 1383, 1384, 1385, 1386,
+ 1387, 1388, 1389, 1390, 1391, 1392, 1393, 1394, 1395, 1396,
+ 1397, 1398, 1399, 1400, 1401, 1402, 1403, 1404, 1406, 1407,
+ 1408, 1409, 1410, 1411, 1412, 1413, 1414, 1415, 1416, 1417,
+ 1418, 1419, 1420, 1421, 1422, 1423, 1424, 1425, 1397, 1426,
+ 1427, 1428, 1376, 1429, 1430, 1431, 1432, 1433, 1434, 1435,
+
+ 1436, 1437, 1438, 1439, 1440, 1441, 1442, 1443, 1444, 1445,
+ 1447, 1405, 1448, 1449, 1450, 1451, 1452, 1446, 1453, 1454,
+ 1455, 1456, 1457, 1458, 1459, 1460, 1461, 1462, 1463, 1464,
+ 1465, 1466, 1467, 1468, 1470, 1471, 1474, 1472, 1469, 1473,
+ 1475, 1476, 1477, 1478, 1479, 1480, 1481, 1482, 1483, 1484,
+ 1485, 1487, 1488, 1489, 1490, 1486, 1491, 1492, 1493, 1494,
+ 1495, 1496, 1497, 1498, 1499, 1500, 1501, 1502, 1503, 1504,
+ 1505, 1506, 1507, 1508, 1487, 1509, 1510, 1511, 1512, 1513,
+ 1514, 1515, 1516, 1517, 1518, 1519, 1520, 1521, 1522, 1523,
+ 1524, 1525, 1526, 1527, 1528, 1529, 1530, 1531, 1532, 1533,
+
+ 1534, 1535, 1536, 1537, 1538, 1539, 1540, 1541, 1542, 1543,
+ 1544, 1545, 1546, 1547, 1548, 1552, 1549, 1553, 1554, 1550,
+ 1555, 1556, 1551, 1557, 1558, 1559, 1560, 1561, 1562, 1563,
+ 1564, 1565, 1566, 1567, 1568, 1569, 1570, 1571, 1572, 1573,
+ 1574, 1575, 1576, 1577, 1579, 1580, 1581, 1582, 1583, 1584,
+ 1585, 1586, 1587, 1588, 1578, 1589, 1590, 1591, 1592, 1566,
+ 1593, 1594, 1595, 1596, 1597, 1598, 1599, 1601, 1602, 1603,
+ 1604, 1605, 1606, 1607, 1608, 1609, 1610, 1611, 1612, 1613,
+ 1614, 1615, 1616, 1617, 1618, 1619, 1620, 1621, 1622, 1623,
+ 1624, 1625, 1626, 1627, 1628, 1629, 1630, 1631, 1632, 1633,
+
+ 1634, 1635, 1636, 1637, 1638, 1639, 1640, 1641, 1642, 1643,
+ 1644, 1645, 1646, 1647, 1648, 1649, 1650, 1651, 1652, 1653,
+ 1654, 1655, 1656, 1657, 1600, 1658, 1659, 1660, 1661, 1662,
+ 1663, 1664, 1665, 1666, 1667, 1668, 1669, 1670, 1644, 1671,
+ 1672, 1673, 1674, 1675, 1676, 1677, 1678, 1679, 1680, 1681,
+ 1682, 1683, 1684, 1685, 1686, 1687, 1688, 1689, 1690, 1691,
+ 1692, 1693, 1694, 1695, 1696, 1697, 1698, 1699, 1700, 1701,
+ 1702, 1703, 1704, 1705, 1706, 1707, 1708, 1709, 1710, 1711,
+ 1712, 1713, 1714, 1715, 1716, 1717, 1718, 1719, 1720, 1721,
+ 1722, 1723, 1724, 1725, 1726, 1727, 1728, 1729, 1730, 1731,
+
+ 1732, 1733, 1734, 1735, 1736, 1737, 1738, 1739, 1740, 1741,
+ 1742, 1743, 1744, 1745, 1746, 1747, 1748, 1749, 1750, 1751,
+ 1752, 1753, 1754, 1755, 1756, 1757, 1758, 1759, 1760, 1762,
+ 1763, 1764, 1765, 1761, 1766, 1767, 1768, 1769, 1770, 1771,
+ 1772, 1773, 1774, 1775, 1776, 1777, 1778, 1779, 1780, 1781,
+ 1782, 1783, 1784, 1785, 1786, 1787, 1788, 1789, 1790, 1791,
+ 1792, 1793, 1794, 1795, 1796, 1797, 1798, 1799, 1800, 1801,
+ 1802, 1803, 1804, 1805, 1806, 1807, 1808, 1809, 1810, 1811,
+ 1812, 1813, 1814, 1815, 1816, 1817, 1818, 1819, 1820, 1821,
+ 1822, 1823, 1824, 1825, 1826, 1827, 1828, 1829, 1830, 1831,
+
+ 1832, 1833, 1834, 1835, 1836, 1837, 1838, 1839, 1840, 1841,
+ 1842, 1843, 1844, 1845, 1846, 1847, 1848, 1849, 1850, 1851,
+ 1853, 1854, 1852, 1855, 1856, 1857, 1858, 1859, 1860, 1861,
+ 1862, 1863, 1864, 1865, 1866, 1867, 1868, 1869, 1870, 1871,
+ 1872, 1873, 1874, 1875, 1876, 1877, 1878, 1879, 1880, 1881,
+ 1882, 1883, 1884, 1885, 1886, 1887, 1888, 1889, 1890, 1891,
+ 1892, 1893, 1894, 1895, 1896, 1897, 1898, 1899, 1900, 1901,
+ 1902, 1903, 1904, 1905, 1906, 1907, 1908, 1909, 1910, 1911,
+ 1912, 1913, 1914, 1915, 1916, 1917, 1918, 1919, 1920, 1921,
+ 1922, 1923, 1924, 1925, 1926, 1927, 1928, 1929, 1930, 1931,
+
+ 1932, 1933, 1934, 1935, 1936, 1937, 1938, 1939, 1940, 1941,
+ 1942, 1943, 1944, 1945, 1946, 1947, 1948, 1949, 1950, 1951,
+ 1952, 1953, 1954, 1955, 1956, 1957, 1958, 1959, 1960, 1961,
+ 1962, 1963, 1964, 1965, 1966, 1967, 1968, 1969, 1970, 1971,
+ 1972, 1973, 1974, 1975, 1976, 1977, 1978, 1979, 1980, 1981,
+ 1982, 1983, 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991,
+ 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
+ 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011,
+ 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2021, 2020,
+ 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030, 2031,
+
+ 2032, 2033, 2034, 2035, 2036, 2037, 2038, 2039, 2040, 2041,
+ 2042, 2043, 2044, 2045, 2046, 2047, 2048, 2049, 2050, 2051,
+ 2052, 2053, 2054, 2055, 2056, 2057, 2058, 2059, 12, 12,
+ 12, 36, 36, 36, 78, 97, 78, 99, 99, 99,
+ 112, 112, 112, 184, 839, 184, 203, 203, 203, 838,
+ 835, 834, 833, 832, 831, 830, 829, 828, 827, 826,
+ 823, 822, 821, 820, 819, 818, 817, 816, 815, 814,
+ 811, 810, 809, 808, 807, 806, 805, 804, 803, 802,
+ 801, 800, 799, 798, 797, 796, 795, 794, 793, 792,
+ 791, 790, 789, 788, 787, 786, 785, 784, 783, 782,
+
+ 781, 780, 777, 776, 775, 774, 773, 772, 771, 770,
+ 769, 768, 767, 766, 765, 764, 763, 762, 761, 760,
+ 759, 758, 757, 756, 753, 752, 751, 750, 749, 748,
+ 747, 746, 745, 744, 743, 742, 741, 740, 739, 738,
+ 737, 736, 735, 734, 733, 732, 731, 730, 729, 728,
+ 725, 724, 723, 722, 721, 720, 719, 718, 717, 716,
+ 715, 714, 713, 712, 711, 710, 709, 706, 705, 701,
+ 700, 699, 698, 697, 696, 695, 694, 693, 692, 691,
+ 690, 689, 688, 687, 686, 685, 684, 683, 682, 681,
+ 678, 677, 676, 675, 674, 673, 672, 671, 670, 669,
+
+ 668, 667, 666, 665, 664, 663, 662, 661, 660, 659,
+ 658, 657, 656, 655, 654, 653, 652, 651, 650, 649,
+ 648, 647, 646, 645, 644, 643, 642, 641, 640, 639,
+ 638, 635, 634, 633, 632, 631, 630, 629, 628, 627,
+ 626, 625, 624, 623, 622, 621, 620, 619, 618, 617,
+ 614, 613, 612, 611, 610, 609, 608, 607, 603, 602,
+ 595, 594, 593, 592, 591, 590, 589, 588, 587, 586,
+ 585, 584, 583, 582, 581, 580, 576, 575, 574, 572,
+ 571, 570, 569, 566, 565, 564, 563, 562, 561, 560,
+ 559, 558, 557, 556, 555, 554, 553, 552, 551, 548,
+
+ 547, 546, 545, 544, 543, 542, 541, 538, 537, 536,
+ 535, 534, 533, 532, 531, 528, 527, 526, 525, 524,
+ 523, 522, 521, 520, 519, 518, 517, 516, 515, 512,
+ 511, 510, 509, 508, 507, 506, 502, 501, 500, 499,
+ 498, 497, 496, 495, 494, 493, 492, 487, 486, 485,
+ 484, 483, 482, 481, 480, 479, 478, 477, 476, 475,
+ 472, 471, 470, 469, 468, 467, 466, 465, 464, 463,
+ 462, 461, 460, 459, 458, 457, 456, 455, 454, 453,
+ 452, 449, 448, 446, 444, 440, 437, 434, 433, 432,
+ 430, 429, 428, 427, 426, 423, 422, 421, 420, 419,
+
+ 418, 417, 416, 415, 414, 413, 412, 411, 410, 407,
+ 406, 405, 404, 403, 402, 401, 400, 399, 398, 397,
+ 396, 393, 392, 388, 387, 386, 385, 384, 383, 382,
+ 381, 380, 379, 378, 374, 373, 372, 371, 370, 369,
+ 368, 365, 364, 363, 362, 361, 360, 359, 358, 357,
+ 356, 355, 354, 353, 352, 351, 350, 349, 348, 347,
+ 346, 345, 344, 338, 337, 336, 335, 334, 333, 332,
+ 331, 330, 329, 328, 326, 204, 323, 321, 317, 315,
+ 314, 307, 306, 305, 303, 301, 300, 299, 298, 297,
+ 294, 293, 292, 277, 271, 266, 265, 264, 261, 259,
+
+ 258, 257, 256, 255, 254, 251, 250, 249, 246, 245,
+ 244, 243, 242, 238, 237, 234, 231, 230, 229, 228,
+ 227, 224, 223, 222, 218, 215, 211, 210, 209, 204,
+ 195, 193, 192, 191, 183, 164, 143, 118, 104, 102,
+ 43, 98, 96, 95, 86, 43, 2060, 11, 2060, 2060,
+ 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060,
+ 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060,
+ 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060,
+ 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060,
+ 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060,
+
+ 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060,
+ 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060,
+ 2060
+ } ;
+
+static const flex_int16_t yy_chk[2522] =
+ { 0,
+ 0, 1, 1, 1, 0, 1, 1, 0, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 21, 653, 3, 3, 3, 21, 1, 1, 3, 44,
+ 718, 1, 4, 4, 4, 13, 1, 13, 4, 27,
+ 28, 1, 106, 1, 5, 5, 5, 26, 32, 1,
+ 1, 33, 6, 6, 6, 7, 7, 7, 1, 7,
+ 653, 8, 8, 8, 1, 8, 44, 27, 28, 106,
+ 26, 1, 1, 2, 2, 2, 32, 2, 2, 33,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 9, 9, 9, 31, 5, 48, 2, 2,
+
+ 10, 10, 10, 2, 6, 37, 50, 37, 2, 42,
+ 51, 42, 9, 2, 52, 2, 54, 55, 31, 64,
+ 10, 2, 2, 48, 127, 127, 25, 64, 25, 50,
+ 2, 25, 25, 25, 25, 25, 2, 52, 51, 54,
+ 121, 55, 121, 2, 2, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 17, 144,
+ 17, 17, 17, 17, 17, 19, 62, 19, 19, 19,
+ 19, 19, 144, 17, 20, 20, 20, 20, 20, 22,
+ 19, 22, 22, 22, 22, 22, 63, 20, 53, 62,
+ 57, 59, 719, 63, 22, 59, 17, 73, 53, 59,
+ 62, 73, 59, 19, 123, 59, 133, 720, 133, 59,
+ 20, 53, 135, 123, 123, 91, 57, 22, 45, 89,
+ 65, 61, 57, 61, 45, 57, 53, 45, 135, 45,
+ 65, 45, 65, 45, 61, 56, 67, 67, 45, 45,
+
+ 67, 45, 56, 91, 61, 89, 67, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 60, 71, 56, 60, 60, 71, 56, 60, 60, 68,
+ 56, 69, 148, 68, 108, 71, 148, 68, 56, 90,
+ 60, 583, 56, 76, 56, 56, 157, 70, 583, 68,
+ 69, 92, 69, 68, 69, 70, 94, 70, 75, 75,
+ 74, 108, 70, 70, 74, 90, 76, 74, 75, 79,
+ 79, 79, 79, 79, 74, 74, 76, 92, 75, 74,
+ 74, 76, 79, 80, 94, 80, 80, 80, 80, 80,
+
+ 164, 75, 157, 75, 164, 81, 75, 81, 80, 99,
+ 81, 81, 81, 81, 81, 79, 75, 83, 83, 83,
+ 83, 83, 82, 75, 82, 82, 82, 82, 82, 93,
+ 83, 80, 87, 87, 87, 87, 87, 82, 88, 88,
+ 88, 88, 88, 101, 103, 105, 99, 107, 109, 119,
+ 101, 110, 161, 83, 111, 93, 138, 115, 119, 138,
+ 82, 155, 119, 138, 167, 167, 180, 161, 161, 161,
+ 178, 105, 103, 172, 115, 107, 109, 111, 110, 163,
+ 101, 185, 163, 163, 101, 115, 168, 172, 101, 155,
+ 168, 155, 186, 180, 168, 721, 101, 187, 178, 188,
+
+ 101, 196, 101, 101, 114, 166, 189, 190, 194, 185,
+ 197, 198, 114, 114, 114, 114, 114, 296, 186, 296,
+ 187, 114, 114, 114, 114, 114, 114, 188, 253, 196,
+ 190, 194, 189, 202, 198, 220, 200, 197, 220, 306,
+ 166, 166, 166, 208, 114, 114, 114, 114, 114, 114,
+ 245, 166, 265, 166, 281, 166, 166, 281, 166, 200,
+ 202, 206, 206, 206, 206, 206, 208, 245, 221, 268,
+ 206, 206, 206, 206, 206, 206, 221, 302, 308, 268,
+ 311, 253, 221, 316, 253, 318, 399, 320, 265, 315,
+ 722, 306, 327, 206, 206, 206, 206, 206, 206, 315,
+
+ 302, 308, 265, 311, 349, 409, 363, 443, 318, 409,
+ 349, 363, 316, 383, 302, 320, 325, 325, 325, 325,
+ 325, 327, 723, 383, 724, 325, 325, 325, 325, 325,
+ 325, 375, 375, 418, 436, 443, 375, 418, 449, 436,
+ 449, 549, 483, 725, 399, 451, 470, 483, 325, 325,
+ 325, 325, 325, 325, 470, 578, 470, 470, 470, 363,
+ 470, 473, 577, 601, 363, 449, 601, 449, 450, 450,
+ 450, 450, 450, 451, 473, 504, 473, 450, 450, 450,
+ 450, 450, 450, 578, 689, 504, 579, 629, 629, 577,
+ 702, 579, 703, 549, 726, 689, 717, 717, 727, 717,
+
+ 450, 450, 450, 450, 450, 450, 714, 728, 729, 730,
+ 731, 732, 714, 733, 734, 703, 735, 736, 702, 737,
+ 738, 739, 740, 737, 741, 742, 744, 737, 745, 746,
+ 747, 748, 749, 750, 751, 752, 753, 754, 755, 756,
+ 757, 758, 759, 760, 761, 762, 763, 764, 762, 765,
+ 767, 768, 769, 770, 771, 772, 773, 774, 775, 776,
+ 777, 779, 780, 781, 782, 783, 784, 785, 786, 787,
+ 788, 789, 790, 791, 792, 793, 794, 795, 796, 797,
+ 798, 799, 800, 801, 802, 803, 804, 804, 805, 806,
+ 807, 802, 809, 808, 804, 810, 811, 813, 804, 808,
+
+ 814, 815, 816, 817, 818, 819, 820, 821, 822, 823,
+ 824, 825, 826, 827, 828, 829, 830, 831, 832, 833,
+ 834, 835, 836, 837, 839, 840, 841, 842, 843, 844,
+ 845, 846, 847, 848, 849, 850, 851, 852, 853, 825,
+ 854, 855, 856, 857, 858, 859, 860, 861, 862, 863,
+ 864, 865, 866, 868, 869, 870, 871, 872, 873, 874,
+ 875, 876, 877, 873, 878, 879, 880, 881, 882, 883,
+ 886, 887, 888, 889, 890, 891, 894, 895, 896, 897,
+ 898, 900, 901, 902, 903, 904, 901, 905, 906, 907,
+ 908, 909, 912, 913, 914, 915, 916, 917, 918, 919,
+
+ 920, 921, 922, 923, 924, 925, 926, 927, 928, 929,
+ 930, 931, 932, 933, 934, 935, 936, 937, 938, 939,
+ 940, 941, 943, 944, 945, 946, 947, 948, 949, 950,
+ 951, 952, 953, 954, 955, 956, 957, 958, 959, 960,
+ 962, 963, 964, 965, 966, 967, 968, 969, 970, 971,
+ 972, 973, 974, 975, 976, 977, 954, 978, 979, 953,
+ 980, 981, 982, 983, 984, 985, 986, 988, 989, 990,
+ 991, 992, 993, 994, 995, 996, 997, 998, 999, 1001,
+ 1002, 1003, 1004, 1005, 1006, 1008, 1009, 1010, 1011, 1012,
+ 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022,
+
+ 1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032,
+ 1034, 1035, 1036, 1037, 1039, 1040, 1041, 1029, 1042, 1043,
+ 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053,
+ 1054, 1055, 1056, 1058, 1059, 1060, 1061, 1062, 1063, 1064,
+ 1004, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073,
+ 1074, 1076, 1075, 1077, 1078, 1079, 1080, 1081, 1083, 1084,
+ 1085, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095,
+ 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1104, 1074, 1075,
+ 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114,
+ 1115, 1116, 1117, 1118, 1119, 1120, 1121, 1123, 1124, 1125,
+
+ 1126, 1127, 1123, 1128, 1129, 1130, 1131, 1132, 1133, 1134,
+ 1135, 1136, 1137, 1138, 1139, 1140, 1141, 1142, 1143, 1144,
+ 1145, 1146, 1147, 1148, 1149, 1150, 1151, 1152, 1153, 1154,
+ 1156, 1157, 1158, 1159, 1160, 1161, 1162, 1163, 1164, 1165,
+ 1167, 1168, 1124, 1169, 1171, 1172, 1173, 1174, 1175, 1176,
+ 1177, 1178, 1179, 1180, 1181, 1182, 1183, 1184, 1185, 1186,
+ 1187, 1188, 1190, 1191, 1192, 1193, 1194, 1195, 1196, 1197,
+ 1198, 1200, 1201, 1202, 1203, 1204, 1205, 1206, 1208, 1209,
+ 1210, 1211, 1212, 1213, 1214, 1190, 1216, 1217, 1218, 1219,
+ 1220, 1191, 1221, 1222, 1223, 1224, 1225, 1226, 1227, 1228,
+
+ 1229, 1230, 1231, 1232, 1233, 1236, 1238, 1239, 1240, 1241,
+ 1242, 1243, 1244, 1245, 1246, 1247, 1248, 1249, 1250, 1251,
+ 1253, 1254, 1255, 1256, 1258, 1259, 1260, 1261, 1262, 1263,
+ 1265, 1267, 1269, 1270, 1271, 1272, 1273, 1274, 1275, 1276,
+ 1277, 1278, 1279, 1280, 1281, 1282, 1284, 1285, 1286, 1287,
+ 1288, 1289, 1290, 1293, 1294, 1295, 1296, 1297, 1298, 1299,
+ 1300, 1301, 1303, 1304, 1305, 1306, 1307, 1309, 1310, 1311,
+ 1312, 1313, 1314, 1315, 1316, 1317, 1318, 1319, 1320, 1321,
+ 1322, 1323, 1324, 1325, 1326, 1327, 1328, 1329, 1300, 1330,
+ 1331, 1333, 1276, 1334, 1335, 1336, 1337, 1338, 1339, 1340,
+
+ 1341, 1342, 1343, 1344, 1345, 1346, 1347, 1348, 1349, 1350,
+ 1351, 1309, 1352, 1353, 1354, 1355, 1356, 1350, 1358, 1361,
+ 1362, 1363, 1364, 1365, 1366, 1367, 1368, 1369, 1370, 1371,
+ 1373, 1374, 1375, 1376, 1377, 1378, 1381, 1380, 1376, 1380,
+ 1383, 1384, 1387, 1388, 1389, 1390, 1391, 1392, 1394, 1395,
+ 1396, 1397, 1399, 1400, 1401, 1396, 1402, 1403, 1405, 1406,
+ 1407, 1408, 1409, 1410, 1411, 1412, 1413, 1414, 1415, 1416,
+ 1417, 1418, 1419, 1420, 1397, 1421, 1423, 1424, 1425, 1427,
+ 1428, 1429, 1430, 1431, 1432, 1433, 1434, 1435, 1436, 1438,
+ 1440, 1441, 1443, 1444, 1445, 1446, 1447, 1448, 1449, 1450,
+
+ 1451, 1453, 1454, 1455, 1456, 1457, 1458, 1459, 1460, 1462,
+ 1463, 1465, 1466, 1467, 1469, 1470, 1469, 1471, 1472, 1469,
+ 1473, 1474, 1469, 1475, 1476, 1477, 1478, 1479, 1480, 1482,
+ 1484, 1486, 1487, 1489, 1491, 1492, 1493, 1495, 1496, 1497,
+ 1498, 1499, 1500, 1501, 1502, 1503, 1504, 1505, 1506, 1507,
+ 1508, 1509, 1510, 1511, 1501, 1512, 1513, 1514, 1515, 1487,
+ 1516, 1517, 1518, 1519, 1520, 1520, 1521, 1522, 1523, 1524,
+ 1525, 1526, 1527, 1528, 1529, 1530, 1531, 1532, 1533, 1534,
+ 1535, 1536, 1537, 1538, 1539, 1540, 1541, 1542, 1543, 1544,
+ 1545, 1546, 1547, 1548, 1549, 1550, 1551, 1552, 1554, 1555,
+
+ 1556, 1557, 1558, 1559, 1560, 1561, 1562, 1563, 1564, 1565,
+ 1566, 1568, 1569, 1572, 1573, 1574, 1576, 1577, 1578, 1579,
+ 1580, 1581, 1582, 1583, 1521, 1584, 1585, 1586, 1587, 1589,
+ 1591, 1592, 1593, 1594, 1595, 1596, 1597, 1598, 1566, 1600,
+ 1601, 1602, 1604, 1605, 1607, 1608, 1609, 1611, 1612, 1613,
+ 1614, 1615, 1617, 1618, 1619, 1620, 1621, 1622, 1623, 1624,
+ 1625, 1626, 1627, 1628, 1629, 1630, 1631, 1632, 1633, 1634,
+ 1635, 1636, 1637, 1638, 1639, 1640, 1641, 1643, 1644, 1646,
+ 1647, 1648, 1650, 1651, 1652, 1653, 1654, 1655, 1656, 1657,
+ 1658, 1659, 1660, 1661, 1662, 1663, 1664, 1665, 1666, 1668,
+
+ 1669, 1670, 1672, 1673, 1674, 1676, 1677, 1678, 1679, 1680,
+ 1682, 1683, 1684, 1685, 1686, 1687, 1688, 1689, 1690, 1691,
+ 1692, 1693, 1694, 1695, 1696, 1697, 1699, 1700, 1702, 1704,
+ 1706, 1707, 1708, 1702, 1711, 1713, 1714, 1715, 1716, 1717,
+ 1718, 1719, 1720, 1721, 1722, 1723, 1725, 1726, 1727, 1728,
+ 1730, 1731, 1732, 1733, 1734, 1735, 1736, 1737, 1739, 1740,
+ 1741, 1743, 1744, 1745, 1746, 1747, 1748, 1749, 1750, 1751,
+ 1752, 1754, 1755, 1756, 1757, 1758, 1760, 1761, 1762, 1763,
+ 1765, 1766, 1767, 1768, 1769, 1770, 1771, 1772, 1774, 1775,
+ 1776, 1777, 1778, 1779, 1780, 1781, 1782, 1783, 1784, 1787,
+
+ 1788, 1789, 1790, 1791, 1792, 1793, 1794, 1795, 1797, 1799,
+ 1800, 1801, 1802, 1803, 1804, 1805, 1806, 1807, 1808, 1809,
+ 1810, 1811, 1809, 1812, 1813, 1814, 1815, 1816, 1817, 1818,
+ 1819, 1820, 1821, 1822, 1824, 1825, 1826, 1827, 1828, 1829,
+ 1830, 1831, 1832, 1834, 1838, 1840, 1841, 1842, 1843, 1844,
+ 1845, 1846, 1847, 1848, 1849, 1850, 1851, 1852, 1853, 1854,
+ 1855, 1857, 1858, 1859, 1860, 1861, 1862, 1863, 1864, 1865,
+ 1866, 1867, 1868, 1869, 1871, 1872, 1873, 1874, 1876, 1878,
+ 1881, 1883, 1884, 1885, 1886, 1887, 1888, 1889, 1890, 1892,
+ 1893, 1894, 1895, 1896, 1897, 1898, 1900, 1901, 1902, 1903,
+
+ 1904, 1905, 1906, 1907, 1908, 1909, 1910, 1911, 1912, 1913,
+ 1914, 1915, 1917, 1918, 1919, 1920, 1921, 1922, 1924, 1925,
+ 1926, 1927, 1928, 1929, 1930, 1931, 1932, 1933, 1934, 1935,
+ 1936, 1937, 1938, 1939, 1940, 1941, 1942, 1943, 1944, 1945,
+ 1946, 1947, 1948, 1950, 1951, 1953, 1954, 1955, 1956, 1958,
+ 1959, 1960, 1961, 1965, 1968, 1969, 1970, 1971, 1972, 1973,
+ 1974, 1975, 1977, 1978, 1979, 1980, 1981, 1982, 1983, 1984,
+ 1986, 1987, 1988, 1989, 1990, 1991, 1993, 1995, 1997, 1998,
+ 1999, 2000, 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2007,
+ 2009, 2010, 2012, 2013, 2016, 2017, 2018, 2019, 2020, 2021,
+
+ 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030, 2032,
+ 2033, 2034, 2035, 2038, 2039, 2040, 2042, 2043, 2045, 2047,
+ 2048, 2049, 2050, 2051, 2052, 2053, 2054, 2056, 2061, 2061,
+ 2061, 2062, 2062, 2062, 2063, 2064, 2063, 2065, 2065, 2065,
+ 2066, 2066, 2066, 2067, 716, 2067, 2068, 2068, 2068, 715,
+ 713, 712, 711, 710, 709, 708, 707, 706, 705, 704,
+ 700, 699, 697, 696, 695, 694, 693, 692, 691, 690,
+ 688, 687, 686, 684, 683, 682, 681, 680, 679, 678,
+ 677, 676, 675, 674, 673, 672, 670, 669, 668, 667,
+ 666, 665, 664, 663, 662, 660, 659, 658, 657, 656,
+
+ 655, 654, 652, 651, 650, 649, 648, 647, 646, 645,
+ 643, 642, 641, 640, 639, 638, 637, 636, 635, 634,
+ 633, 632, 631, 630, 628, 627, 626, 625, 624, 623,
+ 622, 621, 620, 619, 618, 617, 616, 614, 613, 612,
+ 611, 610, 609, 608, 607, 606, 605, 604, 603, 602,
+ 600, 599, 598, 597, 596, 595, 594, 593, 592, 591,
+ 590, 589, 588, 587, 586, 585, 584, 582, 581, 573,
+ 572, 571, 570, 569, 568, 566, 565, 563, 562, 560,
+ 559, 558, 557, 556, 555, 554, 553, 552, 551, 550,
+ 548, 547, 546, 545, 544, 543, 542, 541, 540, 539,
+
+ 538, 537, 536, 535, 534, 533, 532, 530, 528, 527,
+ 526, 525, 524, 523, 522, 521, 520, 519, 518, 517,
+ 516, 515, 514, 513, 511, 510, 509, 508, 507, 506,
+ 505, 503, 502, 501, 500, 499, 498, 497, 496, 495,
+ 494, 493, 492, 491, 490, 489, 487, 486, 485, 484,
+ 482, 481, 480, 479, 478, 477, 475, 474, 472, 471,
+ 468, 467, 466, 465, 464, 463, 461, 460, 459, 458,
+ 457, 456, 455, 454, 453, 452, 447, 445, 444, 442,
+ 441, 440, 437, 435, 434, 432, 431, 430, 429, 428,
+ 427, 426, 425, 424, 423, 422, 421, 420, 419, 417,
+
+ 416, 415, 414, 413, 412, 411, 410, 408, 407, 406,
+ 405, 403, 402, 401, 400, 398, 397, 396, 395, 394,
+ 393, 392, 391, 390, 389, 388, 387, 386, 384, 382,
+ 381, 380, 379, 378, 377, 376, 374, 373, 372, 371,
+ 370, 369, 368, 367, 366, 365, 364, 362, 361, 360,
+ 359, 358, 357, 356, 355, 354, 353, 352, 351, 350,
+ 348, 347, 346, 345, 344, 343, 342, 341, 340, 339,
+ 338, 337, 336, 335, 334, 333, 332, 331, 330, 329,
+ 328, 324, 322, 319, 317, 314, 307, 305, 304, 303,
+ 301, 300, 299, 298, 297, 295, 294, 293, 292, 291,
+
+ 290, 289, 288, 287, 286, 285, 284, 283, 282, 280,
+ 279, 278, 277, 276, 275, 274, 273, 272, 271, 270,
+ 269, 267, 266, 264, 263, 262, 261, 260, 259, 258,
+ 257, 256, 255, 254, 252, 251, 250, 249, 248, 247,
+ 246, 244, 242, 241, 240, 239, 238, 237, 236, 235,
+ 234, 233, 232, 231, 230, 229, 228, 227, 226, 225,
+ 224, 223, 222, 219, 218, 217, 216, 215, 214, 213,
+ 212, 211, 210, 209, 207, 203, 201, 199, 195, 193,
+ 191, 183, 182, 181, 179, 177, 176, 175, 174, 173,
+ 171, 170, 169, 165, 162, 160, 159, 158, 156, 154,
+
+ 153, 152, 151, 150, 149, 147, 146, 145, 143, 142,
+ 141, 140, 139, 137, 136, 134, 132, 131, 130, 129,
+ 128, 126, 125, 124, 122, 120, 118, 117, 116, 112,
+ 104, 102, 97, 96, 77, 72, 66, 58, 49, 47,
+ 43, 41, 39, 38, 24, 14, 11, 2060, 2060, 2060,
+ 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060,
+ 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060,
+ 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060,
+ 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060,
+ 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060,
+
+ 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060,
+ 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060, 2060,
+ 2060
+ } ;
+
+static yy_state_type yy_last_accepting_state;
+static char *yy_last_accepting_cpos;
+
+extern int yy_flex_debug;
+int yy_flex_debug = 1;
+
+static const flex_int16_t yy_rule_linenum[214] =
+ { 0,
+ 146, 148, 150, 155, 156, 161, 162, 163, 175, 178,
+ 183, 190, 199, 208, 217, 226, 235, 244, 254, 263,
+ 272, 281, 290, 299, 308, 317, 326, 335, 344, 353,
+ 362, 371, 380, 389, 401, 410, 419, 428, 439, 450,
+ 461, 472, 483, 494, 505, 516, 527, 536, 545, 554,
+ 565, 574, 585, 596, 607, 618, 630, 642, 654, 665,
+ 676, 685, 694, 703, 712, 721, 732, 743, 754, 765,
+ 776, 787, 798, 809, 819, 830, 839, 849, 863, 879,
+ 888, 897, 906, 915, 937, 959, 968, 978, 987, 998,
+ 1007, 1016, 1025, 1034, 1043, 1054, 1065, 1076, 1086, 1095,
+
+ 1106, 1117, 1128, 1139, 1151, 1160, 1169, 1178, 1187, 1196,
+ 1205, 1214, 1223, 1232, 1242, 1253, 1265, 1274, 1283, 1293,
+ 1303, 1313, 1323, 1333, 1343, 1352, 1362, 1371, 1380, 1389,
+ 1398, 1408, 1418, 1427, 1437, 1446, 1455, 1464, 1473, 1482,
+ 1491, 1500, 1509, 1518, 1527, 1536, 1545, 1554, 1563, 1572,
+ 1581, 1590, 1599, 1608, 1617, 1626, 1635, 1644, 1653, 1662,
+ 1671, 1680, 1689, 1698, 1707, 1716, 1725, 1734, 1743, 1752,
+ 1764, 1776, 1786, 1796, 1806, 1816, 1826, 1836, 1846, 1856,
+ 1866, 1875, 1884, 1893, 1902, 1913, 1924, 1937, 1950, 1963,
+ 1972, 1981, 1990, 1999, 2008, 2109, 2125, 2174, 2182, 2197,
+
+ 2198, 2199, 2200, 2201, 2202, 2204, 2222, 2235, 2240, 2244,
+ 2246, 2248, 2250
+ } ;
+
+/* The intent behind this definition is that it'll catch
+ * any uses of REJECT which flex missed.
+ */
+#define REJECT reject_used_but_not_detected
+#define yymore() yymore_used_but_not_detected
+#define YY_MORE_ADJ 0
+#define YY_RESTORE_YY_MORE_OFFSET
+char *yytext;
+#line 1 "dhcp4_lexer.ll"
+/* Copyright (C) 2016-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/. */
+#line 8 "dhcp4_lexer.ll"
+
+/* Generated files do not make clang static analyser so happy */
+#ifndef __clang_analyzer__
+
+#include <cctype>
+#include <cerrno>
+#include <climits>
+#include <cstdlib>
+#include <string>
+#include <dhcp4/parser_context.h>
+#include <asiolink/io_address.h>
+#include <boost/lexical_cast.hpp>
+#include <exceptions/exceptions.h>
+
+/* Please avoid C++ style comments (// ... eol) as they break flex 2.6.2 */
+
+/* Work around an incompatibility in flex (at least versions
+ 2.5.31 through 2.5.33): it generates code that does
+ not conform to C89. See Debian bug 333231
+ <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=333231>. */
+# undef yywrap
+# define yywrap() 1
+
+namespace {
+
+bool start_token_flag = false;
+
+isc::dhcp::Parser4Context::ParserType start_token_value;
+unsigned int comment_start_line = 0;
+
+using namespace isc::dhcp;
+
+};
+
+/* To avoid the call to exit... oops! */
+#define YY_FATAL_ERROR(msg) isc::dhcp::Parser4Context::fatal(msg)
+#line 2088 "dhcp4_lexer.cc"
+/* noyywrap disables automatic rewinding for the next file to parse. Since we
+ always parse only a single string, there's no need to do any wraps. And
+ using yywrap requires linking with -lfl, which provides the default yywrap
+ implementation that always returns 1 anyway. */
+/* nounput simplifies the lexer, by removing support for putting a character
+ back into the input stream. We never use such capability anyway. */
+/* batch means that we'll never use the generated lexer interactively. */
+/* avoid to get static global variables to remain with C++. */
+/* in last resort %option reentrant */
+/* Enables debug mode. To see the debug messages, one needs to also set
+ yy_flex_debug to 1, then the debug messages will be printed on stderr. */
+/* I have no idea what this option does, except it was specified in the bison
+ examples and Postgres folks added it to remove gcc 4.3 warnings. Let's
+ be on the safe side and keep it. */
+#define YY_NO_INPUT 1
+
+/* These are not token expressions yet, just convenience expressions that
+ can be used during actual token definitions. Note some can match
+ incorrect inputs (e.g., IP addresses) which must be checked. */
+/* for errors */
+#line 95 "dhcp4_lexer.ll"
+/* This code run each time a pattern is matched. It updates the location
+ by moving it ahead by yyleng bytes. yyleng specifies the length of the
+ currently matched token. */
+#define YY_USER_ACTION driver.loc_.columns(yyleng);
+#line 2114 "dhcp4_lexer.cc"
+#line 2115 "dhcp4_lexer.cc"
+
+#define INITIAL 0
+#define COMMENT 1
+#define DIR_ENTER 2
+#define DIR_INCLUDE 3
+#define DIR_EXIT 4
+
+#ifndef YY_NO_UNISTD_H
+/* Special case for "unistd.h", since it is non-ANSI. We include it way
+ * down here because we want the user's section 1 to have been scanned first.
+ * The user has a chance to override it with an option.
+ */
+/* %if-c-only */
+#include <unistd.h>
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+#endif
+
+#ifndef YY_EXTRA_TYPE
+#define YY_EXTRA_TYPE void *
+#endif
+
+/* %if-c-only Reentrant structure and macros (non-C++). */
+/* %if-reentrant */
+/* %if-c-only */
+
+static int yy_init_globals ( void );
+
+/* %endif */
+/* %if-reentrant */
+/* %endif */
+/* %endif End reentrant structures and macros. */
+
+/* Accessor methods to globals.
+ These are made visible to non-reentrant scanners for convenience. */
+
+int yylex_destroy ( void );
+
+int yyget_debug ( void );
+
+void yyset_debug ( int debug_flag );
+
+YY_EXTRA_TYPE yyget_extra ( void );
+
+void yyset_extra ( YY_EXTRA_TYPE user_defined );
+
+FILE *yyget_in ( void );
+
+void yyset_in ( FILE * _in_str );
+
+FILE *yyget_out ( void );
+
+void yyset_out ( FILE * _out_str );
+
+ int yyget_leng ( void );
+
+char *yyget_text ( void );
+
+int yyget_lineno ( void );
+
+void yyset_lineno ( int _line_number );
+
+/* %if-bison-bridge */
+/* %endif */
+
+/* Macros after this point can all be overridden by user definitions in
+ * section 1.
+ */
+
+#ifndef YY_SKIP_YYWRAP
+#ifdef __cplusplus
+extern "C" int yywrap ( void );
+#else
+extern int yywrap ( void );
+#endif
+#endif
+
+/* %not-for-header */
+#ifndef YY_NO_UNPUT
+
+#endif
+/* %ok-for-header */
+
+/* %endif */
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy ( char *, const char *, int );
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen ( const char * );
+#endif
+
+#ifndef YY_NO_INPUT
+/* %if-c-only Standard (non-C++) definition */
+/* %not-for-header */
+#ifdef __cplusplus
+static int yyinput ( void );
+#else
+static int input ( void );
+#endif
+/* %ok-for-header */
+
+/* %endif */
+#endif
+
+/* %if-c-only */
+
+/* %endif */
+
+/* Amount of stuff to slurp up with each read. */
+#ifndef YY_READ_BUF_SIZE
+#ifdef __ia64__
+/* On IA-64, the buffer size is 16k, not 8k */
+#define YY_READ_BUF_SIZE 16384
+#else
+#define YY_READ_BUF_SIZE 8192
+#endif /* __ia64__ */
+#endif
+
+/* Copy whatever the last rule matched to the standard output. */
+#ifndef ECHO
+/* %if-c-only Standard (non-C++) definition */
+/* This used to be an fputs(), but since the string might contain NUL's,
+ * we now use fwrite().
+ */
+#define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0)
+/* %endif */
+/* %if-c++-only C++ definition */
+/* %endif */
+#endif
+
+/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL,
+ * is returned in "result".
+ */
+#ifndef YY_INPUT
+#define YY_INPUT(buf,result,max_size) \
+/* %% [5.0] fread()/read() definition of YY_INPUT goes here unless we're doing C++ \ */\
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \
+ { \
+ int c = '*'; \
+ int n; \
+ for ( n = 0; n < max_size && \
+ (c = getc( yyin )) != EOF && c != '\n'; ++n ) \
+ buf[n] = (char) c; \
+ if ( c == '\n' ) \
+ buf[n++] = (char) c; \
+ if ( c == EOF && ferror( yyin ) ) \
+ YY_FATAL_ERROR( "input in flex scanner failed" ); \
+ result = n; \
+ } \
+ else \
+ { \
+ errno=0; \
+ while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \
+ { \
+ if( errno != EINTR) \
+ { \
+ YY_FATAL_ERROR( "input in flex scanner failed" ); \
+ break; \
+ } \
+ errno=0; \
+ clearerr(yyin); \
+ } \
+ }\
+\
+/* %if-c++-only C++ definition \ */\
+/* %endif */
+
+#endif
+
+/* No semi-colon after return; correct usage is to write "yyterminate();" -
+ * we don't want an extra ';' after the "return" because that will cause
+ * some compilers to complain about unreachable statements.
+ */
+#ifndef yyterminate
+#define yyterminate() return YY_NULL
+#endif
+
+/* Number of entries by which start-condition stack grows. */
+#ifndef YY_START_STACK_INCR
+#define YY_START_STACK_INCR 25
+#endif
+
+/* Report a fatal error. */
+#ifndef YY_FATAL_ERROR
+/* %if-c-only */
+#define YY_FATAL_ERROR(msg) yy_fatal_error( msg )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+#endif
+
+/* %if-tables-serialization structures and prototypes */
+/* %not-for-header */
+/* %ok-for-header */
+
+/* %not-for-header */
+/* %tables-yydmap generated elements */
+/* %endif */
+/* end tables serialization structures and prototypes */
+
+/* %ok-for-header */
+
+/* Default declaration of generated scanner - a define so the user can
+ * easily add parameters.
+ */
+#ifndef YY_DECL
+#define YY_DECL_IS_OURS 1
+/* %if-c-only Standard (non-C++) definition */
+
+extern int yylex (void);
+
+#define YY_DECL int yylex (void)
+/* %endif */
+/* %if-c++-only C++ definition */
+/* %endif */
+#endif /* !YY_DECL */
+
+/* Code executed at the beginning of each rule, after yytext and yyleng
+ * have been set up.
+ */
+#ifndef YY_USER_ACTION
+#define YY_USER_ACTION
+#endif
+
+/* Code executed at the end of each rule. */
+#ifndef YY_BREAK
+#define YY_BREAK /*LINTED*/break;
+#endif
+
+/* %% [6.0] YY_RULE_SETUP definition goes here */
+#define YY_RULE_SETUP \
+ YY_USER_ACTION
+
+/* %not-for-header */
+/** The main scanner function which does all the work.
+ */
+YY_DECL
+{
+ yy_state_type yy_current_state;
+ char *yy_cp, *yy_bp;
+ int yy_act;
+
+ if ( !(yy_init) )
+ {
+ (yy_init) = 1;
+
+#ifdef YY_USER_INIT
+ YY_USER_INIT;
+#endif
+
+ if ( ! (yy_start) )
+ (yy_start) = 1; /* first start state */
+
+ if ( ! yyin )
+/* %if-c-only */
+ yyin = stdin;
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+
+ if ( ! yyout )
+/* %if-c-only */
+ yyout = stdout;
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+
+ if ( ! YY_CURRENT_BUFFER ) {
+ yyensure_buffer_stack ();
+ YY_CURRENT_BUFFER_LVALUE =
+ yy_create_buffer( yyin, YY_BUF_SIZE );
+ }
+
+ yy_load_buffer_state( );
+ }
+
+ {
+/* %% [7.0] user's declarations go here */
+#line 101 "dhcp4_lexer.ll"
+
+
+
+#line 105 "dhcp4_lexer.ll"
+ /* This part of the code is copied over to the verbatim to the top
+ of the generated yylex function. Explanation:
+ http://www.gnu.org/software/bison/manual/html_node/Multiple-start_002dsymbols.html */
+
+ /* Code run each time yylex is called. */
+ driver.loc_.step();
+
+ if (start_token_flag) {
+ start_token_flag = false;
+ switch (start_token_value) {
+ case Parser4Context::PARSER_JSON:
+ default:
+ return isc::dhcp::Dhcp4Parser::make_TOPLEVEL_JSON(driver.loc_);
+ case Parser4Context::PARSER_DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_TOPLEVEL_DHCP4(driver.loc_);
+ case Parser4Context::SUBPARSER_DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_SUB_DHCP4(driver.loc_);
+ case Parser4Context::PARSER_INTERFACES:
+ return isc::dhcp::Dhcp4Parser::make_SUB_INTERFACES4(driver.loc_);
+ case Parser4Context::PARSER_SUBNET4:
+ return isc::dhcp::Dhcp4Parser::make_SUB_SUBNET4(driver.loc_);
+ case Parser4Context::PARSER_POOL4:
+ return isc::dhcp::Dhcp4Parser::make_SUB_POOL4(driver.loc_);
+ case Parser4Context::PARSER_HOST_RESERVATION:
+ return isc::dhcp::Dhcp4Parser::make_SUB_RESERVATION(driver.loc_);
+ case Parser4Context::PARSER_OPTION_DEFS:
+ return isc::dhcp::Dhcp4Parser::make_SUB_OPTION_DEFS(driver.loc_);
+ case Parser4Context::PARSER_OPTION_DEF:
+ return isc::dhcp::Dhcp4Parser::make_SUB_OPTION_DEF(driver.loc_);
+ case Parser4Context::PARSER_OPTION_DATA:
+ return isc::dhcp::Dhcp4Parser::make_SUB_OPTION_DATA(driver.loc_);
+ case Parser4Context::PARSER_HOOKS_LIBRARY:
+ return isc::dhcp::Dhcp4Parser::make_SUB_HOOKS_LIBRARY(driver.loc_);
+ case Parser4Context::PARSER_DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_SUB_DHCP_DDNS(driver.loc_);
+ case Parser4Context::PARSER_CONFIG_CONTROL:
+ return isc::dhcp::Dhcp4Parser::make_SUB_CONFIG_CONTROL(driver.loc_);
+ }
+ }
+
+
+#line 2443 "dhcp4_lexer.cc"
+
+ while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */
+ {
+/* %% [8.0] yymore()-related code goes here */
+ yy_cp = (yy_c_buf_p);
+
+ /* Support of yytext. */
+ *yy_cp = (yy_hold_char);
+
+ /* yy_bp points to the position in yy_ch_buf of the start of
+ * the current run.
+ */
+ yy_bp = yy_cp;
+
+/* %% [9.0] code to set up and find next match goes here */
+ yy_current_state = (yy_start);
+yy_match:
+ do
+ {
+ YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ;
+ if ( yy_accept[yy_current_state] )
+ {
+ (yy_last_accepting_state) = yy_current_state;
+ (yy_last_accepting_cpos) = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 2061 )
+ yy_c = yy_meta[yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
+ ++yy_cp;
+ }
+ while ( yy_current_state != 2060 );
+ yy_cp = (yy_last_accepting_cpos);
+ yy_current_state = (yy_last_accepting_state);
+
+yy_find_action:
+/* %% [10.0] code to find the action number goes here */
+ yy_act = yy_accept[yy_current_state];
+
+ YY_DO_BEFORE_ACTION;
+
+/* %% [11.0] code for yylineno update goes here */
+
+do_action: /* This label is used only to access EOF actions. */
+
+/* %% [12.0] debug code goes here */
+ if ( yy_flex_debug )
+ {
+ if ( yy_act == 0 )
+ fprintf( stderr, "--scanner backing up\n" );
+ else if ( yy_act < 214 )
+ fprintf( stderr, "--accepting rule at line %ld (\"%s\")\n",
+ (long)yy_rule_linenum[yy_act], yytext );
+ else if ( yy_act == 214 )
+ fprintf( stderr, "--accepting default rule (\"%s\")\n",
+ yytext );
+ else if ( yy_act == 215 )
+ fprintf( stderr, "--(end of buffer or a NUL)\n" );
+ else
+ fprintf( stderr, "--EOF (start condition %d)\n", YY_START );
+ }
+
+ switch ( yy_act )
+ { /* beginning of action switch */
+/* %% [13.0] actions go here */
+ case 0: /* must back up */
+ /* undo the effects of YY_DO_BEFORE_ACTION */
+ *yy_cp = (yy_hold_char);
+ yy_cp = (yy_last_accepting_cpos);
+ yy_current_state = (yy_last_accepting_state);
+ goto yy_find_action;
+
+case 1:
+YY_RULE_SETUP
+#line 146 "dhcp4_lexer.ll"
+;
+ YY_BREAK
+case 2:
+YY_RULE_SETUP
+#line 148 "dhcp4_lexer.ll"
+;
+ YY_BREAK
+case 3:
+YY_RULE_SETUP
+#line 150 "dhcp4_lexer.ll"
+{
+ BEGIN(COMMENT);
+ comment_start_line = driver.loc_.end.line;;
+}
+ YY_BREAK
+case 4:
+YY_RULE_SETUP
+#line 155 "dhcp4_lexer.ll"
+BEGIN(INITIAL);
+ YY_BREAK
+case 5:
+YY_RULE_SETUP
+#line 156 "dhcp4_lexer.ll"
+;
+ YY_BREAK
+case YY_STATE_EOF(COMMENT):
+#line 157 "dhcp4_lexer.ll"
+{
+ isc_throw(Dhcp4ParseError, "Comment not closed. (/* in line " << comment_start_line);
+}
+ YY_BREAK
+case 6:
+YY_RULE_SETUP
+#line 161 "dhcp4_lexer.ll"
+BEGIN(DIR_ENTER);
+ YY_BREAK
+case 7:
+YY_RULE_SETUP
+#line 162 "dhcp4_lexer.ll"
+BEGIN(DIR_INCLUDE);
+ YY_BREAK
+case 8:
+YY_RULE_SETUP
+#line 163 "dhcp4_lexer.ll"
+{
+ /* Include directive. */
+
+ /* Extract the filename. */
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+
+ driver.includeFile(tmp);
+}
+ YY_BREAK
+case YY_STATE_EOF(DIR_ENTER):
+case YY_STATE_EOF(DIR_INCLUDE):
+case YY_STATE_EOF(DIR_EXIT):
+#line 172 "dhcp4_lexer.ll"
+{
+ isc_throw(Dhcp4ParseError, "Directive not closed.");
+}
+ YY_BREAK
+case 9:
+YY_RULE_SETUP
+#line 175 "dhcp4_lexer.ll"
+BEGIN(INITIAL);
+ YY_BREAK
+case 10:
+YY_RULE_SETUP
+#line 178 "dhcp4_lexer.ll"
+{
+ /* Ok, we found a with space. Let's ignore it and update loc variable. */
+ driver.loc_.step();
+}
+ YY_BREAK
+case 11:
+/* rule 11 can match eol */
+YY_RULE_SETUP
+#line 183 "dhcp4_lexer.ll"
+{
+ /* Newline found. Let's update the location and continue. */
+ driver.loc_.lines(yyleng);
+ driver.loc_.step();
+}
+ YY_BREAK
+case 12:
+YY_RULE_SETUP
+#line 190 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::CONFIG:
+ return isc::dhcp::Dhcp4Parser::make_DHCP4(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("Dhcp4", driver.loc_);
+ }
+}
+ YY_BREAK
+case 13:
+YY_RULE_SETUP
+#line 199 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_INTERFACES_CONFIG(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("interfaces-config", driver.loc_);
+ }
+}
+ YY_BREAK
+case 14:
+YY_RULE_SETUP
+#line 208 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_SANITY_CHECKS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("sanity-checks", driver.loc_);
+ }
+}
+ YY_BREAK
+case 15:
+YY_RULE_SETUP
+#line 217 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SANITY_CHECKS:
+ return isc::dhcp::Dhcp4Parser::make_LEASE_CHECKS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("lease-checks", driver.loc_);
+ }
+}
+ YY_BREAK
+case 16:
+YY_RULE_SETUP
+#line 226 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::INTERFACES_CONFIG:
+ return isc::dhcp::Dhcp4Parser::make_DHCP_SOCKET_TYPE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("dhcp-socket-type", driver.loc_);
+ }
+}
+ YY_BREAK
+case 17:
+YY_RULE_SETUP
+#line 235 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_SOCKET_TYPE:
+ return isc::dhcp::Dhcp4Parser::make_RAW(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("raw", driver.loc_);
+ }
+}
+ YY_BREAK
+case 18:
+YY_RULE_SETUP
+#line 244 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_SOCKET_TYPE:
+ case isc::dhcp::Parser4Context::NCR_PROTOCOL:
+ return isc::dhcp::Dhcp4Parser::make_UDP(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("udp", driver.loc_);
+ }
+}
+ YY_BREAK
+case 19:
+YY_RULE_SETUP
+#line 254 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case Parser4Context::INTERFACES_CONFIG:
+ return isc::dhcp::Dhcp4Parser::make_OUTBOUND_INTERFACE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("outbound-interface", driver.loc_);
+ }
+}
+ YY_BREAK
+case 20:
+YY_RULE_SETUP
+#line 263 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case Parser4Context::OUTBOUND_INTERFACE:
+ return Dhcp4Parser::make_SAME_AS_INBOUND(driver.loc_);
+ default:
+ return Dhcp4Parser::make_STRING("same-as-inbound", driver.loc_);
+ }
+}
+ YY_BREAK
+case 21:
+YY_RULE_SETUP
+#line 272 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case Parser4Context::OUTBOUND_INTERFACE:
+ return Dhcp4Parser::make_USE_ROUTING(driver.loc_);
+ default:
+ return Dhcp4Parser::make_STRING("use-routing", driver.loc_);
+ }
+}
+ YY_BREAK
+case 22:
+YY_RULE_SETUP
+#line 281 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::INTERFACES_CONFIG:
+ return isc::dhcp::Dhcp4Parser::make_INTERFACES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("interfaces", driver.loc_);
+ }
+}
+ YY_BREAK
+case 23:
+YY_RULE_SETUP
+#line 290 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::INTERFACES_CONFIG:
+ return isc::dhcp::Dhcp4Parser::make_RE_DETECT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("re-detect", driver.loc_);
+ }
+}
+ YY_BREAK
+case 24:
+YY_RULE_SETUP
+#line 299 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::INTERFACES_CONFIG:
+ return isc::dhcp::Dhcp4Parser::make_SERVICE_SOCKETS_REQUIRE_ALL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("service-sockets-require-all", driver.loc_);
+ }
+}
+ YY_BREAK
+case 25:
+YY_RULE_SETUP
+#line 308 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::INTERFACES_CONFIG:
+ return isc::dhcp::Dhcp4Parser::make_SERVICE_SOCKETS_RETRY_WAIT_TIME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("service-sockets-retry-wait-time", driver.loc_);
+ }
+}
+ YY_BREAK
+case 26:
+YY_RULE_SETUP
+#line 317 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::INTERFACES_CONFIG:
+ return isc::dhcp::Dhcp4Parser::make_SERVICE_SOCKETS_MAX_RETRIES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("service-sockets-max-retries", driver.loc_);
+ }
+}
+ YY_BREAK
+case 27:
+YY_RULE_SETUP
+#line 326 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_LEASE_DATABASE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("lease-database", driver.loc_);
+ }
+}
+ YY_BREAK
+case 28:
+YY_RULE_SETUP
+#line 335 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_HOSTS_DATABASE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("hosts-database", driver.loc_);
+ }
+}
+ YY_BREAK
+case 29:
+YY_RULE_SETUP
+#line 344 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_HOSTS_DATABASES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("hosts-databases", driver.loc_);
+ }
+}
+ YY_BREAK
+case 30:
+YY_RULE_SETUP
+#line 353 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_CONFIG_CONTROL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("config-control", driver.loc_);
+ }
+}
+ YY_BREAK
+case 31:
+YY_RULE_SETUP
+#line 362 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::CONFIG_CONTROL:
+ return isc::dhcp::Dhcp4Parser::make_CONFIG_DATABASES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("config-databases", driver.loc_);
+ }
+}
+ YY_BREAK
+case 32:
+YY_RULE_SETUP
+#line 371 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::CONFIG_CONTROL:
+ return isc::dhcp::Dhcp4Parser::make_CONFIG_FETCH_WAIT_TIME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("config-fetch-wait-time", driver.loc_);
+ }
+}
+ YY_BREAK
+case 33:
+YY_RULE_SETUP
+#line 380 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_READONLY(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("readonly", driver.loc_);
+ }
+}
+ YY_BREAK
+case 34:
+YY_RULE_SETUP
+#line 389 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::OPTION_DEF:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_TYPE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("type", driver.loc_);
+ }
+}
+ YY_BREAK
+case 35:
+YY_RULE_SETUP
+#line 401 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DATABASE_TYPE:
+ return isc::dhcp::Dhcp4Parser::make_MEMFILE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("memfile", driver.loc_);
+ }
+}
+ YY_BREAK
+case 36:
+YY_RULE_SETUP
+#line 410 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DATABASE_TYPE:
+ return isc::dhcp::Dhcp4Parser::make_MYSQL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("mysql", driver.loc_);
+ }
+}
+ YY_BREAK
+case 37:
+YY_RULE_SETUP
+#line 419 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DATABASE_TYPE:
+ return isc::dhcp::Dhcp4Parser::make_POSTGRESQL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("postgresql", driver.loc_);
+ }
+}
+ YY_BREAK
+case 38:
+YY_RULE_SETUP
+#line 428 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_USER(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("user", driver.loc_);
+ }
+}
+ YY_BREAK
+case 39:
+YY_RULE_SETUP
+#line 439 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_PASSWORD(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("password", driver.loc_);
+ }
+}
+ YY_BREAK
+case 40:
+YY_RULE_SETUP
+#line 450 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_HOST(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("host", driver.loc_);
+ }
+}
+ YY_BREAK
+case 41:
+YY_RULE_SETUP
+#line 461 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_PORT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("port", driver.loc_);
+ }
+}
+ YY_BREAK
+case 42:
+YY_RULE_SETUP
+#line 472 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_PERSIST(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("persist", driver.loc_);
+ }
+}
+ YY_BREAK
+case 43:
+YY_RULE_SETUP
+#line 483 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_LFC_INTERVAL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("lfc-interval", driver.loc_);
+ }
+}
+ YY_BREAK
+case 44:
+YY_RULE_SETUP
+#line 494 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_CONNECT_TIMEOUT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("connect-timeout", driver.loc_);
+ }
+}
+ YY_BREAK
+case 45:
+YY_RULE_SETUP
+#line 505 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_RECONNECT_WAIT_TIME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("reconnect-wait-time", driver.loc_);
+ }
+}
+ YY_BREAK
+case 46:
+YY_RULE_SETUP
+#line 516 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_ON_FAIL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("on-fail", driver.loc_);
+ }
+}
+ YY_BREAK
+case 47:
+YY_RULE_SETUP
+#line 527 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DATABASE_ON_FAIL:
+ return isc::dhcp::Dhcp4Parser::make_STOP_RETRY_EXIT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("stop-retry-exit", driver.loc_);
+ }
+}
+ YY_BREAK
+case 48:
+YY_RULE_SETUP
+#line 536 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DATABASE_ON_FAIL:
+ return isc::dhcp::Dhcp4Parser::make_SERVE_RETRY_EXIT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("serve-retry-exit", driver.loc_);
+ }
+}
+ YY_BREAK
+case 49:
+YY_RULE_SETUP
+#line 545 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DATABASE_ON_FAIL:
+ return isc::dhcp::Dhcp4Parser::make_SERVE_RETRY_CONTINUE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("serve-retry-continue", driver.loc_);
+ }
+}
+ YY_BREAK
+case 50:
+YY_RULE_SETUP
+#line 554 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_MAX_RECONNECT_TRIES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("max-reconnect-tries", driver.loc_);
+ }
+}
+ YY_BREAK
+case 51:
+YY_RULE_SETUP
+#line 565 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_MAX_ROW_ERRORS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("max-row-errors", driver.loc_);
+ }
+}
+ YY_BREAK
+case 52:
+YY_RULE_SETUP
+#line 574 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_TRUST_ANCHOR(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("trust-anchor", driver.loc_);
+ }
+}
+ YY_BREAK
+case 53:
+YY_RULE_SETUP
+#line 585 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_CERT_FILE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("cert-file", driver.loc_);
+ }
+}
+ YY_BREAK
+case 54:
+YY_RULE_SETUP
+#line 596 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_KEY_FILE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("key-file", driver.loc_);
+ }
+}
+ YY_BREAK
+case 55:
+YY_RULE_SETUP
+#line 607 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_CIPHER_LIST(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("cipher-list", driver.loc_);
+ }
+}
+ YY_BREAK
+case 56:
+YY_RULE_SETUP
+#line 618 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_VALID_LIFETIME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("valid-lifetime", driver.loc_);
+ }
+}
+ YY_BREAK
+case 57:
+YY_RULE_SETUP
+#line 630 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_MIN_VALID_LIFETIME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("min-valid-lifetime", driver.loc_);
+ }
+}
+ YY_BREAK
+case 58:
+YY_RULE_SETUP
+#line 642 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_MAX_VALID_LIFETIME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("max-valid-lifetime", driver.loc_);
+ }
+}
+ YY_BREAK
+case 59:
+YY_RULE_SETUP
+#line 654 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_RENEW_TIMER(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("renew-timer", driver.loc_);
+ }
+}
+ YY_BREAK
+case 60:
+YY_RULE_SETUP
+#line 665 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_REBIND_TIMER(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("rebind-timer", driver.loc_);
+ }
+}
+ YY_BREAK
+case 61:
+YY_RULE_SETUP
+#line 676 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_DECLINE_PROBATION_PERIOD(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("decline-probation-period", driver.loc_);
+ }
+}
+ YY_BREAK
+case 62:
+YY_RULE_SETUP
+#line 685 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_SERVER_TAG(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("server-tag", driver.loc_);
+ }
+}
+ YY_BREAK
+case 63:
+YY_RULE_SETUP
+#line 694 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_PARKED_PACKET_LIMIT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("parked-packet-limit", driver.loc_);
+ }
+}
+ YY_BREAK
+case 64:
+YY_RULE_SETUP
+#line 703 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_STATISTIC_DEFAULT_SAMPLE_COUNT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("statistic-default-sample-count", driver.loc_);
+ }
+}
+ YY_BREAK
+case 65:
+YY_RULE_SETUP
+#line 712 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_STATISTIC_DEFAULT_SAMPLE_AGE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("statistic-default-sample-age", driver.loc_);
+ }
+}
+ YY_BREAK
+case 66:
+YY_RULE_SETUP
+#line 721 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_DDNS_SEND_UPDATES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ddns-send-updates", driver.loc_);
+ }
+}
+ YY_BREAK
+case 67:
+YY_RULE_SETUP
+#line 732 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_DDNS_OVERRIDE_NO_UPDATE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ddns-override-no-update", driver.loc_);
+ }
+}
+ YY_BREAK
+case 68:
+YY_RULE_SETUP
+#line 743 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_DDNS_OVERRIDE_CLIENT_UPDATE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ddns-override-client-update", driver.loc_);
+ }
+}
+ YY_BREAK
+case 69:
+YY_RULE_SETUP
+#line 754 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_DDNS_REPLACE_CLIENT_NAME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ddns-replace-client-name", driver.loc_);
+ }
+}
+ YY_BREAK
+case 70:
+YY_RULE_SETUP
+#line 765 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_DDNS_GENERATED_PREFIX(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ddns-generated-prefix", driver.loc_);
+ }
+}
+ YY_BREAK
+case 71:
+YY_RULE_SETUP
+#line 776 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_DDNS_QUALIFYING_SUFFIX(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ddns-qualifying-suffix", driver.loc_);
+ }
+}
+ YY_BREAK
+case 72:
+YY_RULE_SETUP
+#line 787 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_DDNS_UPDATE_ON_RENEW(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ddns-update-on-renew", driver.loc_);
+ }
+}
+ YY_BREAK
+case 73:
+YY_RULE_SETUP
+#line 798 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_DDNS_USE_CONFLICT_RESOLUTION(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ddns-use-conflict-resolution", driver.loc_);
+ }
+}
+ YY_BREAK
+case 74:
+YY_RULE_SETUP
+#line 809 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_SUBNET4(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("subnet4", driver.loc_);
+ }
+}
+ YY_BREAK
+case 75:
+YY_RULE_SETUP
+#line 819 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_STORE_EXTENDED_INFO(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("store-extended-info", driver.loc_);
+ }
+}
+ YY_BREAK
+case 76:
+YY_RULE_SETUP
+#line 830 "dhcp4_lexer.ll"
+{
+ switch (driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_SHARED_NETWORKS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("shared-networks", driver.loc_);
+ }
+}
+ YY_BREAK
+case 77:
+YY_RULE_SETUP
+#line 839 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_OPTION_DEF(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("option-def", driver.loc_);
+ }
+}
+ YY_BREAK
+case 78:
+YY_RULE_SETUP
+#line 849 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::POOLS:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_OPTION_DATA(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("option-data", driver.loc_);
+ }
+}
+ YY_BREAK
+case 79:
+YY_RULE_SETUP
+#line 863 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ case isc::dhcp::Parser4Context::OPTION_DEF:
+ case isc::dhcp::Parser4Context::OPTION_DATA:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::LOGGERS:
+ return isc::dhcp::Dhcp4Parser::make_NAME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("name", driver.loc_);
+ }
+}
+ YY_BREAK
+case 80:
+YY_RULE_SETUP
+#line 879 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OPTION_DATA:
+ return isc::dhcp::Dhcp4Parser::make_DATA(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("data", driver.loc_);
+ }
+}
+ YY_BREAK
+case 81:
+YY_RULE_SETUP
+#line 888 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OPTION_DATA:
+ return isc::dhcp::Dhcp4Parser::make_ALWAYS_SEND(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("always-send", driver.loc_);
+ }
+}
+ YY_BREAK
+case 82:
+YY_RULE_SETUP
+#line 897 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ return isc::dhcp::Dhcp4Parser::make_POOLS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("pools", driver.loc_);
+ }
+}
+ YY_BREAK
+case 83:
+YY_RULE_SETUP
+#line 906 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::POOLS:
+ return isc::dhcp::Dhcp4Parser::make_POOL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("pool", driver.loc_);
+ }
+}
+ YY_BREAK
+case 84:
+YY_RULE_SETUP
+#line 915 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::INTERFACES_CONFIG:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::POOLS:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::OPTION_DEF:
+ case isc::dhcp::Parser4Context::OPTION_DATA:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ case isc::dhcp::Parser4Context::CONTROL_SOCKET:
+ case isc::dhcp::Parser4Context::DHCP_QUEUE_CONTROL:
+ case isc::dhcp::Parser4Context::DHCP_MULTI_THREADING:
+ case isc::dhcp::Parser4Context::LOGGERS:
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_USER_CONTEXT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("user-context", driver.loc_);
+ }
+}
+ YY_BREAK
+case 85:
+YY_RULE_SETUP
+#line 937 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::INTERFACES_CONFIG:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::POOLS:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::OPTION_DEF:
+ case isc::dhcp::Parser4Context::OPTION_DATA:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ case isc::dhcp::Parser4Context::CONTROL_SOCKET:
+ case isc::dhcp::Parser4Context::DHCP_QUEUE_CONTROL:
+ case isc::dhcp::Parser4Context::DHCP_MULTI_THREADING:
+ case isc::dhcp::Parser4Context::LOGGERS:
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_COMMENT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("comment", driver.loc_);
+ }
+}
+ YY_BREAK
+case 86:
+YY_RULE_SETUP
+#line 959 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ return isc::dhcp::Dhcp4Parser::make_SUBNET(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("subnet", driver.loc_);
+ }
+}
+ YY_BREAK
+case 87:
+YY_RULE_SETUP
+#line 968 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_INTERFACE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("interface", driver.loc_);
+ }
+}
+ YY_BREAK
+case 88:
+YY_RULE_SETUP
+#line 978 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ return isc::dhcp::Dhcp4Parser::make_ID(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("id", driver.loc_);
+ }
+}
+ YY_BREAK
+case 89:
+YY_RULE_SETUP
+#line 987 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_RESERVATION_MODE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("reservation-mode", driver.loc_);
+ }
+}
+ YY_BREAK
+case 90:
+YY_RULE_SETUP
+#line 998 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::RESERVATION_MODE:
+ return isc::dhcp::Dhcp4Parser::make_DISABLED(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("disabled", driver.loc_);
+ }
+}
+ YY_BREAK
+case 91:
+YY_RULE_SETUP
+#line 1007 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::RESERVATION_MODE:
+ return isc::dhcp::Dhcp4Parser::make_DISABLED(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("off", driver.loc_);
+ }
+}
+ YY_BREAK
+case 92:
+YY_RULE_SETUP
+#line 1016 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::RESERVATION_MODE:
+ return isc::dhcp::Dhcp4Parser::make_OUT_OF_POOL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("out-of-pool", driver.loc_);
+ }
+}
+ YY_BREAK
+case 93:
+YY_RULE_SETUP
+#line 1025 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::RESERVATION_MODE:
+ return isc::dhcp::Dhcp4Parser::make_GLOBAL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("global", driver.loc_);
+ }
+}
+ YY_BREAK
+case 94:
+YY_RULE_SETUP
+#line 1034 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::RESERVATION_MODE:
+ return isc::dhcp::Dhcp4Parser::make_ALL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("all", driver.loc_);
+ }
+}
+ YY_BREAK
+case 95:
+YY_RULE_SETUP
+#line 1043 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_RESERVATIONS_GLOBAL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("reservations-global", driver.loc_);
+ }
+}
+ YY_BREAK
+case 96:
+YY_RULE_SETUP
+#line 1054 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_RESERVATIONS_IN_SUBNET(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("reservations-in-subnet", driver.loc_);
+ }
+}
+ YY_BREAK
+case 97:
+YY_RULE_SETUP
+#line 1065 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_RESERVATIONS_OUT_OF_POOL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("reservations-out-of-pool", driver.loc_);
+ }
+}
+ YY_BREAK
+case 98:
+YY_RULE_SETUP
+#line 1076 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OPTION_DEF:
+ case isc::dhcp::Parser4Context::OPTION_DATA:
+ return isc::dhcp::Dhcp4Parser::make_CODE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("code", driver.loc_);
+ }
+}
+ YY_BREAK
+case 99:
+YY_RULE_SETUP
+#line 1086 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_HOST_RESERVATION_IDENTIFIERS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("host-reservation-identifiers", driver.loc_);
+ }
+}
+ YY_BREAK
+case 100:
+YY_RULE_SETUP
+#line 1095 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_CALCULATE_TEE_TIMES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("calculate-tee-times", driver.loc_);
+ }
+}
+ YY_BREAK
+case 101:
+YY_RULE_SETUP
+#line 1106 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_T1_PERCENT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("t1-percent", driver.loc_);
+ }
+}
+ YY_BREAK
+case 102:
+YY_RULE_SETUP
+#line 1117 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_T2_PERCENT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("t2-percent", driver.loc_);
+ }
+}
+ YY_BREAK
+case 103:
+YY_RULE_SETUP
+#line 1128 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_CACHE_THRESHOLD(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("cache-threshold", driver.loc_);
+ }
+}
+ YY_BREAK
+case 104:
+YY_RULE_SETUP
+#line 1139 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_CACHE_MAX_AGE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("cache-max-age", driver.loc_);
+ }
+}
+ YY_BREAK
+case 105:
+YY_RULE_SETUP
+#line 1151 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_LOGGERS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("loggers", driver.loc_);
+ }
+}
+ YY_BREAK
+case 106:
+YY_RULE_SETUP
+#line 1160 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LOGGERS:
+ return isc::dhcp::Dhcp4Parser::make_OUTPUT_OPTIONS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("output_options", driver.loc_);
+ }
+}
+ YY_BREAK
+case 107:
+YY_RULE_SETUP
+#line 1169 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OUTPUT_OPTIONS:
+ return isc::dhcp::Dhcp4Parser::make_OUTPUT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("output", driver.loc_);
+ }
+}
+ YY_BREAK
+case 108:
+YY_RULE_SETUP
+#line 1178 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LOGGERS:
+ return isc::dhcp::Dhcp4Parser::make_DEBUGLEVEL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("debuglevel", driver.loc_);
+ }
+}
+ YY_BREAK
+case 109:
+YY_RULE_SETUP
+#line 1187 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OUTPUT_OPTIONS:
+ return isc::dhcp::Dhcp4Parser::make_FLUSH(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("flush", driver.loc_);
+ }
+}
+ YY_BREAK
+case 110:
+YY_RULE_SETUP
+#line 1196 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OUTPUT_OPTIONS:
+ return isc::dhcp::Dhcp4Parser::make_MAXSIZE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("maxsize", driver.loc_);
+ }
+}
+ YY_BREAK
+case 111:
+YY_RULE_SETUP
+#line 1205 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OUTPUT_OPTIONS:
+ return isc::dhcp::Dhcp4Parser::make_MAXVER(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("maxver", driver.loc_);
+ }
+}
+ YY_BREAK
+case 112:
+YY_RULE_SETUP
+#line 1214 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OUTPUT_OPTIONS:
+ return isc::dhcp::Dhcp4Parser::make_PATTERN(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("pattern", driver.loc_);
+ }
+}
+ YY_BREAK
+case 113:
+YY_RULE_SETUP
+#line 1223 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LOGGERS:
+ return isc::dhcp::Dhcp4Parser::make_SEVERITY(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("severity", driver.loc_);
+ }
+}
+ YY_BREAK
+case 114:
+YY_RULE_SETUP
+#line 1232 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ return isc::dhcp::Dhcp4Parser::make_CLIENT_CLASSES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("client-classes", driver.loc_);
+ }
+}
+ YY_BREAK
+case 115:
+YY_RULE_SETUP
+#line 1242 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::POOLS:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_REQUIRE_CLIENT_CLASSES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("require-client-classes", driver.loc_);
+ }
+}
+ YY_BREAK
+case 116:
+YY_RULE_SETUP
+#line 1253 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::POOLS:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_CLIENT_CLASS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("client-class", driver.loc_);
+ }
+}
+ YY_BREAK
+case 117:
+YY_RULE_SETUP
+#line 1265 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_TEST(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("test", driver.loc_);
+ }
+}
+ YY_BREAK
+case 118:
+YY_RULE_SETUP
+#line 1274 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_ONLY_IF_REQUIRED(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("only-if-required", driver.loc_);
+ }
+}
+ YY_BREAK
+case 119:
+YY_RULE_SETUP
+#line 1283 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ return isc::dhcp::Dhcp4Parser::make_RESERVATIONS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("reservations", driver.loc_);
+ }
+}
+ YY_BREAK
+case 120:
+YY_RULE_SETUP
+#line 1293 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::HOST_RESERVATION_IDENTIFIERS:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ return isc::dhcp::Dhcp4Parser::make_DUID(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("duid", driver.loc_);
+ }
+}
+ YY_BREAK
+case 121:
+YY_RULE_SETUP
+#line 1303 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::HOST_RESERVATION_IDENTIFIERS:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ return isc::dhcp::Dhcp4Parser::make_HW_ADDRESS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("hw-address", driver.loc_);
+ }
+}
+ YY_BREAK
+case 122:
+YY_RULE_SETUP
+#line 1313 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::HOST_RESERVATION_IDENTIFIERS:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ return isc::dhcp::Dhcp4Parser::make_CLIENT_ID(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("client-id", driver.loc_);
+ }
+}
+ YY_BREAK
+case 123:
+YY_RULE_SETUP
+#line 1323 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::HOST_RESERVATION_IDENTIFIERS:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ return isc::dhcp::Dhcp4Parser::make_CIRCUIT_ID(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("circuit-id", driver.loc_);
+ }
+}
+ YY_BREAK
+case 124:
+YY_RULE_SETUP
+#line 1333 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::HOST_RESERVATION_IDENTIFIERS:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ return isc::dhcp::Dhcp4Parser::make_FLEX_ID(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("flex-id", driver.loc_);
+ }
+}
+ YY_BREAK
+case 125:
+YY_RULE_SETUP
+#line 1343 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ return isc::dhcp::Dhcp4Parser::make_HOSTNAME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("hostname", driver.loc_);
+ }
+}
+ YY_BREAK
+case 126:
+YY_RULE_SETUP
+#line 1352 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OPTION_DEF:
+ case isc::dhcp::Parser4Context::OPTION_DATA:
+ return isc::dhcp::Dhcp4Parser::make_SPACE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("space", driver.loc_);
+ }
+}
+ YY_BREAK
+case 127:
+YY_RULE_SETUP
+#line 1362 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OPTION_DATA:
+ return isc::dhcp::Dhcp4Parser::make_CSV_FORMAT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("csv-format", driver.loc_);
+ }
+}
+ YY_BREAK
+case 128:
+YY_RULE_SETUP
+#line 1371 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OPTION_DEF:
+ return isc::dhcp::Dhcp4Parser::make_RECORD_TYPES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("record-types", driver.loc_);
+ }
+}
+ YY_BREAK
+case 129:
+YY_RULE_SETUP
+#line 1380 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OPTION_DEF:
+ return isc::dhcp::Dhcp4Parser::make_ENCAPSULATE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("encapsulate", driver.loc_);
+ }
+}
+ YY_BREAK
+case 130:
+YY_RULE_SETUP
+#line 1389 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OPTION_DEF:
+ return isc::dhcp::Dhcp4Parser::make_ARRAY(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("array", driver.loc_);
+ }
+}
+ YY_BREAK
+case 131:
+YY_RULE_SETUP
+#line 1398 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_RELAY(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("relay", driver.loc_);
+ }
+}
+ YY_BREAK
+case 132:
+YY_RULE_SETUP
+#line 1408 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::RELAY:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ return isc::dhcp::Dhcp4Parser::make_IP_ADDRESS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ip-address", driver.loc_);
+ }
+}
+ YY_BREAK
+case 133:
+YY_RULE_SETUP
+#line 1418 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::RELAY:
+ return isc::dhcp::Dhcp4Parser::make_IP_ADDRESSES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ip-addresses", driver.loc_);
+ }
+}
+ YY_BREAK
+case 134:
+YY_RULE_SETUP
+#line 1427 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_HOOKS_LIBRARIES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("hooks-libraries", driver.loc_);
+ }
+}
+ YY_BREAK
+case 135:
+YY_RULE_SETUP
+#line 1437 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::HOOKS_LIBRARIES:
+ return isc::dhcp::Dhcp4Parser::make_PARAMETERS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("parameters", driver.loc_);
+ }
+}
+ YY_BREAK
+case 136:
+YY_RULE_SETUP
+#line 1446 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::HOOKS_LIBRARIES:
+ return isc::dhcp::Dhcp4Parser::make_LIBRARY(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("library", driver.loc_);
+ }
+}
+ YY_BREAK
+case 137:
+YY_RULE_SETUP
+#line 1455 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_EXPIRED_LEASES_PROCESSING(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("expired-leases-processing", driver.loc_);
+ }
+}
+ YY_BREAK
+case 138:
+YY_RULE_SETUP
+#line 1464 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::EXPIRED_LEASES_PROCESSING:
+ return isc::dhcp::Dhcp4Parser::make_RECLAIM_TIMER_WAIT_TIME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("reclaim-timer-wait-time", driver.loc_);
+ }
+}
+ YY_BREAK
+case 139:
+YY_RULE_SETUP
+#line 1473 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::EXPIRED_LEASES_PROCESSING:
+ return isc::dhcp::Dhcp4Parser::make_FLUSH_RECLAIMED_TIMER_WAIT_TIME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("flush-reclaimed-timer-wait-time", driver.loc_);
+ }
+}
+ YY_BREAK
+case 140:
+YY_RULE_SETUP
+#line 1482 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::EXPIRED_LEASES_PROCESSING:
+ return isc::dhcp::Dhcp4Parser::make_HOLD_RECLAIMED_TIME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("hold-reclaimed-time", driver.loc_);
+ }
+}
+ YY_BREAK
+case 141:
+YY_RULE_SETUP
+#line 1491 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::EXPIRED_LEASES_PROCESSING:
+ return isc::dhcp::Dhcp4Parser::make_MAX_RECLAIM_LEASES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("max-reclaim-leases", driver.loc_);
+ }
+}
+ YY_BREAK
+case 142:
+YY_RULE_SETUP
+#line 1500 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::EXPIRED_LEASES_PROCESSING:
+ return isc::dhcp::Dhcp4Parser::make_MAX_RECLAIM_TIME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("max-reclaim-time", driver.loc_);
+ }
+}
+ YY_BREAK
+case 143:
+YY_RULE_SETUP
+#line 1509 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::EXPIRED_LEASES_PROCESSING:
+ return isc::dhcp::Dhcp4Parser::make_UNWARNED_RECLAIM_CYCLES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("unwarned-reclaim-cycles", driver.loc_);
+ }
+}
+ YY_BREAK
+case 144:
+YY_RULE_SETUP
+#line 1518 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_DHCP4O6_PORT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("dhcp4o6-port", driver.loc_);
+ }
+}
+ YY_BREAK
+case 145:
+YY_RULE_SETUP
+#line 1527 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_DHCP_MULTI_THREADING(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("multi-threading", driver.loc_);
+ }
+}
+ YY_BREAK
+case 146:
+YY_RULE_SETUP
+#line 1536 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_MULTI_THREADING:
+ return isc::dhcp::Dhcp4Parser::make_ENABLE_MULTI_THREADING(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("enable-multi-threading", driver.loc_);
+ }
+}
+ YY_BREAK
+case 147:
+YY_RULE_SETUP
+#line 1545 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_MULTI_THREADING:
+ return isc::dhcp::Dhcp4Parser::make_THREAD_POOL_SIZE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("thread-pool-size", driver.loc_);
+ }
+}
+ YY_BREAK
+case 148:
+YY_RULE_SETUP
+#line 1554 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_MULTI_THREADING:
+ return isc::dhcp::Dhcp4Parser::make_PACKET_QUEUE_SIZE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("packet-queue-size", driver.loc_);
+ }
+}
+ YY_BREAK
+case 149:
+YY_RULE_SETUP
+#line 1563 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_CONTROL_SOCKET(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("control-socket", driver.loc_);
+ }
+}
+ YY_BREAK
+case 150:
+YY_RULE_SETUP
+#line 1572 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::CONTROL_SOCKET:
+ return isc::dhcp::Dhcp4Parser::make_SOCKET_TYPE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("socket-type", driver.loc_);
+ }
+}
+ YY_BREAK
+case 151:
+YY_RULE_SETUP
+#line 1581 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::CONTROL_SOCKET:
+ return isc::dhcp::Dhcp4Parser::make_SOCKET_NAME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("socket-name", driver.loc_);
+ }
+}
+ YY_BREAK
+case 152:
+YY_RULE_SETUP
+#line 1590 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_DHCP_QUEUE_CONTROL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("dhcp-queue-control", driver.loc_);
+ }
+}
+ YY_BREAK
+case 153:
+YY_RULE_SETUP
+#line 1599 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_QUEUE_CONTROL:
+ return isc::dhcp::Dhcp4Parser::make_ENABLE_QUEUE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("enable-queue", driver.loc_);
+ }
+}
+ YY_BREAK
+case 154:
+YY_RULE_SETUP
+#line 1608 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_QUEUE_CONTROL:
+ return isc::dhcp::Dhcp4Parser::make_QUEUE_TYPE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("queue-type", driver.loc_);
+ }
+}
+ YY_BREAK
+case 155:
+YY_RULE_SETUP
+#line 1617 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_QUEUE_CONTROL:
+ return isc::dhcp::Dhcp4Parser::make_CAPACITY(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("capacity", driver.loc_);
+ }
+}
+ YY_BREAK
+case 156:
+YY_RULE_SETUP
+#line 1626 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_DHCP_DDNS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("dhcp-ddns", driver.loc_);
+ }
+}
+ YY_BREAK
+case 157:
+YY_RULE_SETUP
+#line 1635 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_ENABLE_UPDATES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("enable-updates", driver.loc_);
+ }
+}
+ YY_BREAK
+case 158:
+YY_RULE_SETUP
+#line 1644 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_QUALIFYING_SUFFIX(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("qualifying-suffix", driver.loc_);
+ }
+}
+ YY_BREAK
+case 159:
+YY_RULE_SETUP
+#line 1653 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_SERVER_IP(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("server-ip", driver.loc_);
+ }
+}
+ YY_BREAK
+case 160:
+YY_RULE_SETUP
+#line 1662 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_SERVER_PORT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("server-port", driver.loc_);
+ }
+}
+ YY_BREAK
+case 161:
+YY_RULE_SETUP
+#line 1671 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_SENDER_IP(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("sender-ip", driver.loc_);
+ }
+}
+ YY_BREAK
+case 162:
+YY_RULE_SETUP
+#line 1680 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_SENDER_PORT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("sender-port", driver.loc_);
+ }
+}
+ YY_BREAK
+case 163:
+YY_RULE_SETUP
+#line 1689 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_MAX_QUEUE_SIZE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("max-queue-size", driver.loc_);
+ }
+}
+ YY_BREAK
+case 164:
+YY_RULE_SETUP
+#line 1698 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_NCR_PROTOCOL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ncr-protocol", driver.loc_);
+ }
+}
+ YY_BREAK
+case 165:
+YY_RULE_SETUP
+#line 1707 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_NCR_FORMAT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ncr-format", driver.loc_);
+ }
+}
+ YY_BREAK
+case 166:
+YY_RULE_SETUP
+#line 1716 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_OVERRIDE_NO_UPDATE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("override-no-update", driver.loc_);
+ }
+}
+ YY_BREAK
+case 167:
+YY_RULE_SETUP
+#line 1725 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_OVERRIDE_CLIENT_UPDATE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("override-client-update", driver.loc_);
+ }
+}
+ YY_BREAK
+case 168:
+YY_RULE_SETUP
+#line 1734 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_REPLACE_CLIENT_NAME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("replace-client-name", driver.loc_);
+ }
+}
+ YY_BREAK
+case 169:
+YY_RULE_SETUP
+#line 1743 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_GENERATED_PREFIX(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("generated-prefix", driver.loc_);
+ }
+}
+ YY_BREAK
+case 170:
+YY_RULE_SETUP
+#line 1752 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_HOSTNAME_CHAR_SET(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("hostname-char-set", driver.loc_);
+ }
+}
+ YY_BREAK
+case 171:
+YY_RULE_SETUP
+#line 1764 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_HOSTNAME_CHAR_REPLACEMENT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("hostname-char-replacement", driver.loc_);
+ }
+}
+ YY_BREAK
+case 172:
+YY_RULE_SETUP
+#line 1776 "dhcp4_lexer.ll"
+{
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::dhcp::Parser4Context::NCR_PROTOCOL) {
+ return isc::dhcp::Dhcp4Parser::make_UDP(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::dhcp::Dhcp4Parser::make_STRING(tmp, driver.loc_);
+}
+ YY_BREAK
+case 173:
+YY_RULE_SETUP
+#line 1786 "dhcp4_lexer.ll"
+{
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::dhcp::Parser4Context::NCR_PROTOCOL) {
+ return isc::dhcp::Dhcp4Parser::make_TCP(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::dhcp::Dhcp4Parser::make_STRING(tmp, driver.loc_);
+}
+ YY_BREAK
+case 174:
+YY_RULE_SETUP
+#line 1796 "dhcp4_lexer.ll"
+{
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::dhcp::Parser4Context::NCR_FORMAT) {
+ return isc::dhcp::Dhcp4Parser::make_JSON(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::dhcp::Dhcp4Parser::make_STRING(tmp, driver.loc_);
+}
+ YY_BREAK
+case 175:
+YY_RULE_SETUP
+#line 1806 "dhcp4_lexer.ll"
+{
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::dhcp::Parser4Context::REPLACE_CLIENT_NAME) {
+ return isc::dhcp::Dhcp4Parser::make_WHEN_PRESENT(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::dhcp::Dhcp4Parser::make_STRING(tmp, driver.loc_);
+}
+ YY_BREAK
+case 176:
+YY_RULE_SETUP
+#line 1816 "dhcp4_lexer.ll"
+{
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::dhcp::Parser4Context::REPLACE_CLIENT_NAME) {
+ return isc::dhcp::Dhcp4Parser::make_WHEN_PRESENT(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::dhcp::Dhcp4Parser::make_STRING(tmp, driver.loc_);
+}
+ YY_BREAK
+case 177:
+YY_RULE_SETUP
+#line 1826 "dhcp4_lexer.ll"
+{
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::dhcp::Parser4Context::REPLACE_CLIENT_NAME) {
+ return isc::dhcp::Dhcp4Parser::make_NEVER(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::dhcp::Dhcp4Parser::make_STRING(tmp, driver.loc_);
+}
+ YY_BREAK
+case 178:
+YY_RULE_SETUP
+#line 1836 "dhcp4_lexer.ll"
+{
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::dhcp::Parser4Context::REPLACE_CLIENT_NAME) {
+ return isc::dhcp::Dhcp4Parser::make_NEVER(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::dhcp::Dhcp4Parser::make_STRING(tmp, driver.loc_);
+}
+ YY_BREAK
+case 179:
+YY_RULE_SETUP
+#line 1846 "dhcp4_lexer.ll"
+{
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::dhcp::Parser4Context::REPLACE_CLIENT_NAME) {
+ return isc::dhcp::Dhcp4Parser::make_ALWAYS(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::dhcp::Dhcp4Parser::make_STRING(tmp, driver.loc_);
+}
+ YY_BREAK
+case 180:
+YY_RULE_SETUP
+#line 1856 "dhcp4_lexer.ll"
+{
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::dhcp::Parser4Context::REPLACE_CLIENT_NAME) {
+ return isc::dhcp::Dhcp4Parser::make_WHEN_NOT_PRESENT(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::dhcp::Dhcp4Parser::make_STRING(tmp, driver.loc_);
+}
+ YY_BREAK
+case 181:
+YY_RULE_SETUP
+#line 1866 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ return isc::dhcp::Dhcp4Parser::make_SUBNET_4O6_INTERFACE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("4o6-interface", driver.loc_);
+ }
+}
+ YY_BREAK
+case 182:
+YY_RULE_SETUP
+#line 1875 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ return isc::dhcp::Dhcp4Parser::make_SUBNET_4O6_INTERFACE_ID(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("4o6-interface-id", driver.loc_);
+ }
+}
+ YY_BREAK
+case 183:
+YY_RULE_SETUP
+#line 1884 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ return isc::dhcp::Dhcp4Parser::make_SUBNET_4O6_SUBNET(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("4o6-subnet", driver.loc_);
+ }
+}
+ YY_BREAK
+case 184:
+YY_RULE_SETUP
+#line 1893 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_ECHO_CLIENT_ID(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("echo-client-id", driver.loc_);
+ }
+}
+ YY_BREAK
+case 185:
+YY_RULE_SETUP
+#line 1902 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_MATCH_CLIENT_ID(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("match-client-id", driver.loc_);
+ }
+}
+ YY_BREAK
+case 186:
+YY_RULE_SETUP
+#line 1913 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_AUTHORITATIVE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("authoritative", driver.loc_);
+ }
+}
+ YY_BREAK
+case 187:
+YY_RULE_SETUP
+#line 1924 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_NEXT_SERVER(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("next-server", driver.loc_);
+ }
+}
+ YY_BREAK
+case 188:
+YY_RULE_SETUP
+#line 1937 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_SERVER_HOSTNAME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("server-hostname", driver.loc_);
+ }
+}
+ YY_BREAK
+case 189:
+YY_RULE_SETUP
+#line 1950 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_BOOT_FILE_NAME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("boot-file-name", driver.loc_);
+ }
+}
+ YY_BREAK
+case 190:
+YY_RULE_SETUP
+#line 1963 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_EARLY_GLOBAL_RESERVATIONS_LOOKUP(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("early-global-reservations-lookup", driver.loc_);
+ }
+}
+ YY_BREAK
+case 191:
+YY_RULE_SETUP
+#line 1972 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_IP_RESERVATIONS_UNIQUE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ip-reservations-unique", driver.loc_);
+ }
+}
+ YY_BREAK
+case 192:
+YY_RULE_SETUP
+#line 1981 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_RESERVATIONS_LOOKUP_FIRST(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("reservations-lookup-first", driver.loc_);
+ }
+}
+ YY_BREAK
+case 193:
+YY_RULE_SETUP
+#line 1990 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_COMPATIBILITY(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("compatibility", driver.loc_);
+ }
+}
+ YY_BREAK
+case 194:
+YY_RULE_SETUP
+#line 1999 "dhcp4_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::COMPATIBILITY:
+ return isc::dhcp::Dhcp4Parser::make_LENIENT_OPTION_PARSING(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("lenient-option-parsing", driver.loc_);
+ }
+}
+ YY_BREAK
+case 195:
+YY_RULE_SETUP
+#line 2008 "dhcp4_lexer.ll"
+{
+ /* A string has been matched. It contains the actual string and single quotes.
+ We need to get those quotes out of the way and just use its content, e.g.
+ for 'foo' we should get foo */
+ std::string raw(yytext+1);
+ size_t len = raw.size() - 1;
+ raw.resize(len);
+ std::string decoded;
+ decoded.reserve(len);
+ for (size_t pos = 0; pos < len; ++pos) {
+ int b = 0;
+ char c = raw[pos];
+ switch (c) {
+ case '"':
+ /* impossible condition */
+ driver.error(driver.loc_, "Bad quote in \"" + raw + "\"");
+ break;
+ case '\\':
+ ++pos;
+ if (pos >= len) {
+ /* impossible condition */
+ driver.error(driver.loc_, "Overflow escape in \"" + raw + "\"");
+ }
+ c = raw[pos];
+ switch (c) {
+ case '"':
+ case '\\':
+ case '/':
+ decoded.push_back(c);
+ break;
+ case 'b':
+ decoded.push_back('\b');
+ break;
+ case 'f':
+ decoded.push_back('\f');
+ break;
+ case 'n':
+ decoded.push_back('\n');
+ break;
+ case 'r':
+ decoded.push_back('\r');
+ break;
+ case 't':
+ decoded.push_back('\t');
+ break;
+ case 'u':
+ /* support only \u0000 to \u00ff */
+ ++pos;
+ if (pos + 4 > len) {
+ /* impossible condition */
+ driver.error(driver.loc_,
+ "Overflow unicode escape in \"" + raw + "\"");
+ }
+ if ((raw[pos] != '0') || (raw[pos + 1] != '0')) {
+ driver.error(driver.loc_,
+ "Unsupported unicode escape in \"" + raw + "\"",
+ pos + 1);
+ }
+ pos += 2;
+ c = raw[pos];
+ if ((c >= '0') && (c <= '9')) {
+ b = (c - '0') << 4;
+ } else if ((c >= 'A') && (c <= 'F')) {
+ b = (c - 'A' + 10) << 4;
+ } else if ((c >= 'a') && (c <= 'f')) {
+ b = (c - 'a' + 10) << 4;
+ } else {
+ /* impossible condition */
+ driver.error(driver.loc_, "Not hexadecimal in unicode escape in \"" + raw + "\"");
+ }
+ pos++;
+ c = raw[pos];
+ if ((c >= '0') && (c <= '9')) {
+ b |= c - '0';
+ } else if ((c >= 'A') && (c <= 'F')) {
+ b |= c - 'A' + 10;
+ } else if ((c >= 'a') && (c <= 'f')) {
+ b |= c - 'a' + 10;
+ } else {
+ /* impossible condition */
+ driver.error(driver.loc_, "Not hexadecimal in unicode escape in \"" + raw + "\"");
+ }
+ decoded.push_back(static_cast<char>(b & 0xff));
+ break;
+ default:
+ /* impossible condition */
+ driver.error(driver.loc_, "Bad escape in \"" + raw + "\"");
+ }
+ break;
+ default:
+ if ((c >= 0) && (c < 0x20)) {
+ /* impossible condition */
+ driver.error(driver.loc_, "Invalid control in \"" + raw + "\"");
+ }
+ decoded.push_back(c);
+ }
+ }
+
+ return isc::dhcp::Dhcp4Parser::make_STRING(decoded, driver.loc_);
+}
+ YY_BREAK
+case 196:
+/* rule 196 can match eol */
+YY_RULE_SETUP
+#line 2109 "dhcp4_lexer.ll"
+{
+ /* Bad string with a forbidden control character inside */
+ std::string raw(yytext+1);
+ size_t len = raw.size() - 1;
+ size_t pos = 0;
+ for (; pos < len; ++pos) {
+ char c = raw[pos];
+ if ((c >= 0) && (c < 0x20)) {
+ break;
+ }
+ }
+ driver.error(driver.loc_,
+ "Invalid control in " + std::string(yytext),
+ pos + 1);
+}
+ YY_BREAK
+case 197:
+/* rule 197 can match eol */
+YY_RULE_SETUP
+#line 2125 "dhcp4_lexer.ll"
+{
+ /* Bad string with a bad escape inside */
+ std::string raw(yytext+1);
+ size_t len = raw.size() - 1;
+ size_t pos = 0;
+ bool found = false;
+ for (; pos < len; ++pos) {
+ if (found) {
+ break;
+ }
+ char c = raw[pos];
+ if (c == '\\') {
+ ++pos;
+ c = raw[pos];
+ switch (c) {
+ case '"':
+ case '\\':
+ case '/':
+ case 'b':
+ case 'f':
+ case 'n':
+ case 'r':
+ case 't':
+ break;
+ case 'u':
+ if ((pos + 4 > len) ||
+ !std::isxdigit(raw[pos + 1]) ||
+ !std::isxdigit(raw[pos + 2]) ||
+ !std::isxdigit(raw[pos + 3]) ||
+ !std::isxdigit(raw[pos + 4])) {
+ found = true;
+ }
+ break;
+ default:
+ found = true;
+ break;
+ }
+ }
+ }
+ /* The rule stops on the first " including on \" so add ... in this case */
+ std::string trailer = "";
+ if (raw[len - 1] == '\\') {
+ trailer = "...";
+ }
+ driver.error(driver.loc_,
+ "Bad escape in " + std::string(yytext) + trailer,
+ pos);
+}
+ YY_BREAK
+case 198:
+YY_RULE_SETUP
+#line 2174 "dhcp4_lexer.ll"
+{
+ /* Bad string with an open escape at the end */
+ std::string raw(yytext+1);
+ driver.error(driver.loc_,
+ "Overflow escape in " + std::string(yytext),
+ raw.size() + 1);
+}
+ YY_BREAK
+case 199:
+YY_RULE_SETUP
+#line 2182 "dhcp4_lexer.ll"
+{
+ /* Bad string with an open unicode escape at the end */
+ std::string raw(yytext+1);
+ size_t pos = raw.size() - 1;
+ for (; pos > 0; --pos) {
+ char c = raw[pos];
+ if (c == 'u') {
+ break;
+ }
+ }
+ driver.error(driver.loc_,
+ "Overflow unicode escape in " + std::string(yytext),
+ pos + 1);
+}
+ YY_BREAK
+case 200:
+YY_RULE_SETUP
+#line 2197 "dhcp4_lexer.ll"
+{ return isc::dhcp::Dhcp4Parser::make_LSQUARE_BRACKET(driver.loc_); }
+ YY_BREAK
+case 201:
+YY_RULE_SETUP
+#line 2198 "dhcp4_lexer.ll"
+{ return isc::dhcp::Dhcp4Parser::make_RSQUARE_BRACKET(driver.loc_); }
+ YY_BREAK
+case 202:
+YY_RULE_SETUP
+#line 2199 "dhcp4_lexer.ll"
+{ return isc::dhcp::Dhcp4Parser::make_LCURLY_BRACKET(driver.loc_); }
+ YY_BREAK
+case 203:
+YY_RULE_SETUP
+#line 2200 "dhcp4_lexer.ll"
+{ return isc::dhcp::Dhcp4Parser::make_RCURLY_BRACKET(driver.loc_); }
+ YY_BREAK
+case 204:
+YY_RULE_SETUP
+#line 2201 "dhcp4_lexer.ll"
+{ return isc::dhcp::Dhcp4Parser::make_COMMA(driver.loc_); }
+ YY_BREAK
+case 205:
+YY_RULE_SETUP
+#line 2202 "dhcp4_lexer.ll"
+{ return isc::dhcp::Dhcp4Parser::make_COLON(driver.loc_); }
+ YY_BREAK
+case 206:
+YY_RULE_SETUP
+#line 2204 "dhcp4_lexer.ll"
+{
+ /* An integer was found. */
+ std::string tmp(yytext);
+ int64_t integer = 0;
+ try {
+ /* In substring we want to use negative values (e.g. -1).
+ In enterprise-id we need to use values up to 0xffffffff.
+ To cover both of those use cases, we need at least
+ int64_t. */
+ integer = boost::lexical_cast<int64_t>(tmp);
+ } catch (const boost::bad_lexical_cast &) {
+ driver.error(driver.loc_, "Failed to convert " + tmp + " to an integer.");
+ }
+
+ /* The parser needs the string form as double conversion is no lossless */
+ return isc::dhcp::Dhcp4Parser::make_INTEGER(integer, driver.loc_);
+}
+ YY_BREAK
+case 207:
+YY_RULE_SETUP
+#line 2222 "dhcp4_lexer.ll"
+{
+ /* A floating point was found. */
+ std::string tmp(yytext);
+ double fp = 0.0;
+ try {
+ fp = boost::lexical_cast<double>(tmp);
+ } catch (const boost::bad_lexical_cast &) {
+ driver.error(driver.loc_, "Failed to convert " + tmp + " to a floating point.");
+ }
+
+ return isc::dhcp::Dhcp4Parser::make_FLOAT(fp, driver.loc_);
+}
+ YY_BREAK
+case 208:
+YY_RULE_SETUP
+#line 2235 "dhcp4_lexer.ll"
+{
+ string tmp(yytext);
+ return isc::dhcp::Dhcp4Parser::make_BOOLEAN(tmp == "true", driver.loc_);
+}
+ YY_BREAK
+case 209:
+YY_RULE_SETUP
+#line 2240 "dhcp4_lexer.ll"
+{
+ return isc::dhcp::Dhcp4Parser::make_NULL_TYPE(driver.loc_);
+}
+ YY_BREAK
+case 210:
+YY_RULE_SETUP
+#line 2244 "dhcp4_lexer.ll"
+driver.error (driver.loc_, "JSON true reserved keyword is lower case only");
+ YY_BREAK
+case 211:
+YY_RULE_SETUP
+#line 2246 "dhcp4_lexer.ll"
+driver.error (driver.loc_, "JSON false reserved keyword is lower case only");
+ YY_BREAK
+case 212:
+YY_RULE_SETUP
+#line 2248 "dhcp4_lexer.ll"
+driver.error (driver.loc_, "JSON null reserved keyword is lower case only");
+ YY_BREAK
+case 213:
+YY_RULE_SETUP
+#line 2250 "dhcp4_lexer.ll"
+driver.error (driver.loc_, "Invalid character: " + std::string(yytext));
+ YY_BREAK
+case YY_STATE_EOF(INITIAL):
+#line 2252 "dhcp4_lexer.ll"
+{
+ if (driver.states_.empty()) {
+ return isc::dhcp::Dhcp4Parser::make_END(driver.loc_);
+ }
+ driver.loc_ = driver.locs_.back();
+ driver.locs_.pop_back();
+ driver.file_ = driver.files_.back();
+ driver.files_.pop_back();
+ if (driver.sfile_) {
+ fclose(driver.sfile_);
+ driver.sfile_ = 0;
+ }
+ if (!driver.sfiles_.empty()) {
+ driver.sfile_ = driver.sfiles_.back();
+ driver.sfiles_.pop_back();
+ }
+ parser4__delete_buffer(YY_CURRENT_BUFFER);
+ parser4__switch_to_buffer(driver.states_.back());
+ driver.states_.pop_back();
+
+ BEGIN(DIR_EXIT);
+}
+ YY_BREAK
+case 214:
+YY_RULE_SETUP
+#line 2275 "dhcp4_lexer.ll"
+ECHO;
+ YY_BREAK
+#line 5310 "dhcp4_lexer.cc"
+
+ case YY_END_OF_BUFFER:
+ {
+ /* Amount of text matched not including the EOB char. */
+ int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1;
+
+ /* Undo the effects of YY_DO_BEFORE_ACTION. */
+ *yy_cp = (yy_hold_char);
+ YY_RESTORE_YY_MORE_OFFSET
+
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW )
+ {
+ /* We're scanning a new file or input source. It's
+ * possible that this happened because the user
+ * just pointed yyin at a new source and called
+ * yylex(). If so, then we have to assure
+ * consistency between YY_CURRENT_BUFFER and our
+ * globals. Here is the right place to do so, because
+ * this is the first action (other than possibly a
+ * back-up) that will match for the new input source.
+ */
+ (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+/* %if-c-only */
+ YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin;
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+ YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL;
+ }
+
+ /* Note that here we test for yy_c_buf_p "<=" to the position
+ * of the first EOB in the buffer, since yy_c_buf_p will
+ * already have been incremented past the NUL character
+ * (since all states make transitions on EOB to the
+ * end-of-buffer state). Contrast this with the test
+ * in input().
+ */
+ if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] )
+ { /* This was really a NUL. */
+ yy_state_type yy_next_state;
+
+ (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text;
+
+ yy_current_state = yy_get_previous_state( );
+
+ /* Okay, we're now positioned to make the NUL
+ * transition. We couldn't have
+ * yy_get_previous_state() go ahead and do it
+ * for us because it doesn't know how to deal
+ * with the possibility of jamming (and we don't
+ * want to build jamming into it because then it
+ * will run more slowly).
+ */
+
+ yy_next_state = yy_try_NUL_trans( yy_current_state );
+
+ yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+
+ if ( yy_next_state )
+ {
+ /* Consume the NUL. */
+ yy_cp = ++(yy_c_buf_p);
+ yy_current_state = yy_next_state;
+ goto yy_match;
+ }
+
+ else
+ {
+/* %% [14.0] code to do back-up for compressed tables and set up yy_cp goes here */
+ yy_cp = (yy_last_accepting_cpos);
+ yy_current_state = (yy_last_accepting_state);
+ goto yy_find_action;
+ }
+ }
+
+ else switch ( yy_get_next_buffer( ) )
+ {
+ case EOB_ACT_END_OF_FILE:
+ {
+ (yy_did_buffer_switch_on_eof) = 0;
+
+ if ( yywrap( ) )
+ {
+ /* Note: because we've taken care in
+ * yy_get_next_buffer() to have set up
+ * yytext, we can now set up
+ * yy_c_buf_p so that if some total
+ * hoser (like flex itself) wants to
+ * call the scanner after we return the
+ * YY_NULL, it'll still work - another
+ * YY_NULL will get returned.
+ */
+ (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ;
+
+ yy_act = YY_STATE_EOF(YY_START);
+ goto do_action;
+ }
+
+ else
+ {
+ if ( ! (yy_did_buffer_switch_on_eof) )
+ YY_NEW_FILE;
+ }
+ break;
+ }
+
+ case EOB_ACT_CONTINUE_SCAN:
+ (yy_c_buf_p) =
+ (yytext_ptr) + yy_amount_of_matched_text;
+
+ yy_current_state = yy_get_previous_state( );
+
+ yy_cp = (yy_c_buf_p);
+ yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+ goto yy_match;
+
+ case EOB_ACT_LAST_MATCH:
+ (yy_c_buf_p) =
+ &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)];
+
+ yy_current_state = yy_get_previous_state( );
+
+ yy_cp = (yy_c_buf_p);
+ yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+ goto yy_find_action;
+ }
+ break;
+ }
+
+ default:
+ YY_FATAL_ERROR(
+ "fatal flex scanner internal error--no action found" );
+ } /* end of action switch */
+ } /* end of scanning one token */
+ } /* end of user's declarations */
+} /* end of yylex */
+/* %ok-for-header */
+
+/* %if-c++-only */
+/* %not-for-header */
+/* %ok-for-header */
+
+/* %endif */
+
+/* yy_get_next_buffer - try to read in a new buffer
+ *
+ * Returns a code representing an action:
+ * EOB_ACT_LAST_MATCH -
+ * EOB_ACT_CONTINUE_SCAN - continue scanning from current position
+ * EOB_ACT_END_OF_FILE - end of file
+ */
+/* %if-c-only */
+static int yy_get_next_buffer (void)
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf;
+ char *source = (yytext_ptr);
+ int number_to_move, i;
+ int ret_val;
+
+ if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] )
+ YY_FATAL_ERROR(
+ "fatal flex scanner internal error--end of buffer missed" );
+
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 )
+ { /* Don't try to fill the buffer, so this is an EOF. */
+ if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 )
+ {
+ /* We matched a single character, the EOB, so
+ * treat this as a final EOF.
+ */
+ return EOB_ACT_END_OF_FILE;
+ }
+
+ else
+ {
+ /* We matched some text prior to the EOB, first
+ * process it.
+ */
+ return EOB_ACT_LAST_MATCH;
+ }
+ }
+
+ /* Try to read more data. */
+
+ /* First move last chars to start of buffer. */
+ number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr) - 1);
+
+ for ( i = 0; i < number_to_move; ++i )
+ *(dest++) = *(source++);
+
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING )
+ /* don't do the read, it's not guaranteed to return an EOF,
+ * just force an EOF
+ */
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0;
+
+ else
+ {
+ int num_to_read =
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1;
+
+ while ( num_to_read <= 0 )
+ { /* Not enough room in the buffer - grow it. */
+
+ /* just a shorter name for the current buffer */
+ YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE;
+
+ int yy_c_buf_p_offset =
+ (int) ((yy_c_buf_p) - b->yy_ch_buf);
+
+ if ( b->yy_is_our_buffer )
+ {
+ int new_size = b->yy_buf_size * 2;
+
+ if ( new_size <= 0 )
+ b->yy_buf_size += b->yy_buf_size / 8;
+ else
+ b->yy_buf_size *= 2;
+
+ b->yy_ch_buf = (char *)
+ /* Include room in for 2 EOB chars. */
+ yyrealloc( (void *) b->yy_ch_buf,
+ (yy_size_t) (b->yy_buf_size + 2) );
+ }
+ else
+ /* Can't grow it, we don't own it. */
+ b->yy_ch_buf = NULL;
+
+ if ( ! b->yy_ch_buf )
+ YY_FATAL_ERROR(
+ "fatal error - scanner input buffer overflow" );
+
+ (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset];
+
+ num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size -
+ number_to_move - 1;
+
+ }
+
+ if ( num_to_read > YY_READ_BUF_SIZE )
+ num_to_read = YY_READ_BUF_SIZE;
+
+ /* Read in more data. */
+ YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]),
+ (yy_n_chars), num_to_read );
+
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+ }
+
+ if ( (yy_n_chars) == 0 )
+ {
+ if ( number_to_move == YY_MORE_ADJ )
+ {
+ ret_val = EOB_ACT_END_OF_FILE;
+ yyrestart( yyin );
+ }
+
+ else
+ {
+ ret_val = EOB_ACT_LAST_MATCH;
+ YY_CURRENT_BUFFER_LVALUE->yy_buffer_status =
+ YY_BUFFER_EOF_PENDING;
+ }
+ }
+
+ else
+ ret_val = EOB_ACT_CONTINUE_SCAN;
+
+ if (((yy_n_chars) + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) {
+ /* Extend the array by 50%, plus the number we really need. */
+ int new_size = (yy_n_chars) + number_to_move + ((yy_n_chars) >> 1);
+ YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc(
+ (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size );
+ if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" );
+ /* "- 2" to take care of EOB's */
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2);
+ }
+
+ (yy_n_chars) += number_to_move;
+ YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR;
+ YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR;
+
+ (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0];
+
+ return ret_val;
+}
+
+/* yy_get_previous_state - get the state just before the EOB char was reached */
+
+/* %if-c-only */
+/* %not-for-header */
+ static yy_state_type yy_get_previous_state (void)
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ yy_state_type yy_current_state;
+ char *yy_cp;
+
+/* %% [15.0] code to get the start state into yy_current_state goes here */
+ yy_current_state = (yy_start);
+
+ for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp )
+ {
+/* %% [16.0] code to find the next state goes here */
+ YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1);
+ if ( yy_accept[yy_current_state] )
+ {
+ (yy_last_accepting_state) = yy_current_state;
+ (yy_last_accepting_cpos) = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 2061 )
+ yy_c = yy_meta[yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
+ }
+
+ return yy_current_state;
+}
+
+/* yy_try_NUL_trans - try to make a transition on the NUL character
+ *
+ * synopsis
+ * next_state = yy_try_NUL_trans( current_state );
+ */
+/* %if-c-only */
+ static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ int yy_is_jam;
+ /* %% [17.0] code to find the next state, and perhaps do backing up, goes here */
+ char *yy_cp = (yy_c_buf_p);
+
+ YY_CHAR yy_c = 1;
+ if ( yy_accept[yy_current_state] )
+ {
+ (yy_last_accepting_state) = yy_current_state;
+ (yy_last_accepting_cpos) = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 2061 )
+ yy_c = yy_meta[yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
+ yy_is_jam = (yy_current_state == 2060);
+
+ return yy_is_jam ? 0 : yy_current_state;
+}
+
+#ifndef YY_NO_UNPUT
+/* %if-c-only */
+
+/* %endif */
+#endif
+
+/* %if-c-only */
+#ifndef YY_NO_INPUT
+#ifdef __cplusplus
+ static int yyinput (void)
+#else
+ static int input (void)
+#endif
+
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ int c;
+
+ *(yy_c_buf_p) = (yy_hold_char);
+
+ if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR )
+ {
+ /* yy_c_buf_p now points to the character we want to return.
+ * If this occurs *before* the EOB characters, then it's a
+ * valid NUL; if not, then we've hit the end of the buffer.
+ */
+ if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] )
+ /* This was really a NUL. */
+ *(yy_c_buf_p) = '\0';
+
+ else
+ { /* need more input */
+ int offset = (int) ((yy_c_buf_p) - (yytext_ptr));
+ ++(yy_c_buf_p);
+
+ switch ( yy_get_next_buffer( ) )
+ {
+ case EOB_ACT_LAST_MATCH:
+ /* This happens because yy_g_n_b()
+ * sees that we've accumulated a
+ * token and flags that we need to
+ * try matching the token before
+ * proceeding. But for input(),
+ * there's no matching to consider.
+ * So convert the EOB_ACT_LAST_MATCH
+ * to EOB_ACT_END_OF_FILE.
+ */
+
+ /* Reset buffer status. */
+ yyrestart( yyin );
+
+ /*FALLTHROUGH*/
+
+ case EOB_ACT_END_OF_FILE:
+ {
+ if ( yywrap( ) )
+ return 0;
+
+ if ( ! (yy_did_buffer_switch_on_eof) )
+ YY_NEW_FILE;
+#ifdef __cplusplus
+ return yyinput();
+#else
+ return input();
+#endif
+ }
+
+ case EOB_ACT_CONTINUE_SCAN:
+ (yy_c_buf_p) = (yytext_ptr) + offset;
+ break;
+ }
+ }
+ }
+
+ c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */
+ *(yy_c_buf_p) = '\0'; /* preserve yytext */
+ (yy_hold_char) = *++(yy_c_buf_p);
+
+/* %% [19.0] update BOL and yylineno */
+
+ return c;
+}
+/* %if-c-only */
+#endif /* ifndef YY_NO_INPUT */
+/* %endif */
+
+/** Immediately switch to a different input stream.
+ * @param input_file A readable stream.
+ *
+ * @note This function does not reset the start condition to @c INITIAL .
+ */
+/* %if-c-only */
+ void yyrestart (FILE * input_file )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+
+ if ( ! YY_CURRENT_BUFFER ){
+ yyensure_buffer_stack ();
+ YY_CURRENT_BUFFER_LVALUE =
+ yy_create_buffer( yyin, YY_BUF_SIZE );
+ }
+
+ yy_init_buffer( YY_CURRENT_BUFFER, input_file );
+ yy_load_buffer_state( );
+}
+
+/* %if-c++-only */
+/* %endif */
+
+/** Switch to a different input buffer.
+ * @param new_buffer The new input buffer.
+ *
+ */
+/* %if-c-only */
+ void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+
+ /* TODO. We should be able to replace this entire function body
+ * with
+ * yypop_buffer_state();
+ * yypush_buffer_state(new_buffer);
+ */
+ yyensure_buffer_stack ();
+ if ( YY_CURRENT_BUFFER == new_buffer )
+ return;
+
+ if ( YY_CURRENT_BUFFER )
+ {
+ /* Flush out information for old buffer. */
+ *(yy_c_buf_p) = (yy_hold_char);
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p);
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+ }
+
+ YY_CURRENT_BUFFER_LVALUE = new_buffer;
+ yy_load_buffer_state( );
+
+ /* We don't actually know whether we did this switch during
+ * EOF (yywrap()) processing, but the only time this flag
+ * is looked at is after yywrap() is called, so it's safe
+ * to go ahead and always set it.
+ */
+ (yy_did_buffer_switch_on_eof) = 1;
+}
+
+/* %if-c-only */
+static void yy_load_buffer_state (void)
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+ (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos;
+/* %if-c-only */
+ yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file;
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+ (yy_hold_char) = *(yy_c_buf_p);
+}
+
+/** Allocate and initialize an input buffer state.
+ * @param file A readable stream.
+ * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE.
+ *
+ * @return the allocated buffer state.
+ */
+/* %if-c-only */
+ YY_BUFFER_STATE yy_create_buffer (FILE * file, int size )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ YY_BUFFER_STATE b;
+
+ b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) );
+ if ( ! b )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+ b->yy_buf_size = size;
+
+ /* yy_ch_buf has to be 2 characters longer than the size given because
+ * we need to put in 2 end-of-buffer characters.
+ */
+ b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) );
+ if ( ! b->yy_ch_buf )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+ b->yy_is_our_buffer = 1;
+
+ yy_init_buffer( b, file );
+
+ return b;
+}
+
+/* %if-c++-only */
+/* %endif */
+
+/** Destroy the buffer.
+ * @param b a buffer created with yy_create_buffer()
+ *
+ */
+/* %if-c-only */
+ void yy_delete_buffer (YY_BUFFER_STATE b )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+
+ if ( ! b )
+ return;
+
+ if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */
+ YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0;
+
+ if ( b->yy_is_our_buffer )
+ yyfree( (void *) b->yy_ch_buf );
+
+ yyfree( (void *) b );
+}
+
+/* Initializes or reinitializes a buffer.
+ * This function is sometimes called more than once on the same buffer,
+ * such as during a yyrestart() or at EOF.
+ */
+/* %if-c-only */
+ static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+
+{
+ int oerrno = errno;
+
+ yy_flush_buffer( b );
+
+/* %if-c-only */
+ b->yy_input_file = file;
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+ b->yy_fill_buffer = 1;
+
+ /* If b is the current buffer, then yy_init_buffer was _probably_
+ * called from yyrestart() or through yy_get_next_buffer.
+ * In that case, we don't want to reset the lineno or column.
+ */
+ if (b != YY_CURRENT_BUFFER){
+ b->yy_bs_lineno = 1;
+ b->yy_bs_column = 0;
+ }
+
+/* %if-c-only */
+
+ b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0;
+
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+ errno = oerrno;
+}
+
+/** Discard all buffered characters. On the next scan, YY_INPUT will be called.
+ * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER.
+ *
+ */
+/* %if-c-only */
+ void yy_flush_buffer (YY_BUFFER_STATE b )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ if ( ! b )
+ return;
+
+ b->yy_n_chars = 0;
+
+ /* We always need two end-of-buffer characters. The first causes
+ * a transition to the end-of-buffer state. The second causes
+ * a jam in that state.
+ */
+ b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR;
+ b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR;
+
+ b->yy_buf_pos = &b->yy_ch_buf[0];
+
+ b->yy_at_bol = 1;
+ b->yy_buffer_status = YY_BUFFER_NEW;
+
+ if ( b == YY_CURRENT_BUFFER )
+ yy_load_buffer_state( );
+}
+
+/* %if-c-or-c++ */
+/** Pushes the new state onto the stack. The new state becomes
+ * the current state. This function will allocate the stack
+ * if necessary.
+ * @param new_buffer The new state.
+ *
+ */
+/* %if-c-only */
+void yypush_buffer_state (YY_BUFFER_STATE new_buffer )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ if (new_buffer == NULL)
+ return;
+
+ yyensure_buffer_stack();
+
+ /* This block is copied from yy_switch_to_buffer. */
+ if ( YY_CURRENT_BUFFER )
+ {
+ /* Flush out information for old buffer. */
+ *(yy_c_buf_p) = (yy_hold_char);
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p);
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+ }
+
+ /* Only push if top exists. Otherwise, replace top. */
+ if (YY_CURRENT_BUFFER)
+ (yy_buffer_stack_top)++;
+ YY_CURRENT_BUFFER_LVALUE = new_buffer;
+
+ /* copied from yy_switch_to_buffer. */
+ yy_load_buffer_state( );
+ (yy_did_buffer_switch_on_eof) = 1;
+}
+/* %endif */
+
+/* %if-c-or-c++ */
+/** Removes and deletes the top of the stack, if present.
+ * The next element becomes the new top.
+ *
+ */
+/* %if-c-only */
+void yypop_buffer_state (void)
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ if (!YY_CURRENT_BUFFER)
+ return;
+
+ yy_delete_buffer(YY_CURRENT_BUFFER );
+ YY_CURRENT_BUFFER_LVALUE = NULL;
+ if ((yy_buffer_stack_top) > 0)
+ --(yy_buffer_stack_top);
+
+ if (YY_CURRENT_BUFFER) {
+ yy_load_buffer_state( );
+ (yy_did_buffer_switch_on_eof) = 1;
+ }
+}
+/* %endif */
+
+/* %if-c-or-c++ */
+/* Allocates the stack if it does not exist.
+ * Guarantees space for at least one push.
+ */
+/* %if-c-only */
+static void yyensure_buffer_stack (void)
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ yy_size_t num_to_alloc;
+
+ if (!(yy_buffer_stack)) {
+
+ /* First allocation is just for 2 elements, since we don't know if this
+ * scanner will even need a stack. We use 2 instead of 1 to avoid an
+ * immediate realloc on the next call.
+ */
+ num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */
+ (yy_buffer_stack) = (struct yy_buffer_state**)yyalloc
+ (num_to_alloc * sizeof(struct yy_buffer_state*)
+ );
+ if ( ! (yy_buffer_stack) )
+ YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" );
+
+ memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*));
+
+ (yy_buffer_stack_max) = num_to_alloc;
+ (yy_buffer_stack_top) = 0;
+ return;
+ }
+
+ if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){
+
+ /* Increase the buffer to prepare for a possible push. */
+ yy_size_t grow_size = 8 /* arbitrary grow size */;
+
+ num_to_alloc = (yy_buffer_stack_max) + grow_size;
+ (yy_buffer_stack) = (struct yy_buffer_state**)yyrealloc
+ ((yy_buffer_stack),
+ num_to_alloc * sizeof(struct yy_buffer_state*)
+ );
+ if ( ! (yy_buffer_stack) )
+ YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" );
+
+ /* zero only the new slots.*/
+ memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*));
+ (yy_buffer_stack_max) = num_to_alloc;
+ }
+}
+/* %endif */
+
+/* %if-c-only */
+/** Setup the input buffer state to scan directly from a user-specified character buffer.
+ * @param base the character buffer
+ * @param size the size in bytes of the character buffer
+ *
+ * @return the newly allocated buffer state object.
+ */
+YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size )
+{
+ YY_BUFFER_STATE b;
+
+ if ( size < 2 ||
+ base[size-2] != YY_END_OF_BUFFER_CHAR ||
+ base[size-1] != YY_END_OF_BUFFER_CHAR )
+ /* They forgot to leave room for the EOB's. */
+ return NULL;
+
+ b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) );
+ if ( ! b )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" );
+
+ b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */
+ b->yy_buf_pos = b->yy_ch_buf = base;
+ b->yy_is_our_buffer = 0;
+ b->yy_input_file = NULL;
+ b->yy_n_chars = b->yy_buf_size;
+ b->yy_is_interactive = 0;
+ b->yy_at_bol = 1;
+ b->yy_fill_buffer = 0;
+ b->yy_buffer_status = YY_BUFFER_NEW;
+
+ yy_switch_to_buffer( b );
+
+ return b;
+}
+/* %endif */
+
+/* %if-c-only */
+/** Setup the input buffer state to scan a string. The next call to yylex() will
+ * scan from a @e copy of @a str.
+ * @param yystr a NUL-terminated string to scan
+ *
+ * @return the newly allocated buffer state object.
+ * @note If you want to scan bytes that may contain NUL values, then use
+ * yy_scan_bytes() instead.
+ */
+YY_BUFFER_STATE yy_scan_string (const char * yystr )
+{
+
+ return yy_scan_bytes( yystr, (int) strlen(yystr) );
+}
+/* %endif */
+
+/* %if-c-only */
+/** Setup the input buffer state to scan the given bytes. The next call to yylex() will
+ * scan from a @e copy of @a bytes.
+ * @param yybytes the byte buffer to scan
+ * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes.
+ *
+ * @return the newly allocated buffer state object.
+ */
+YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, int _yybytes_len )
+{
+ YY_BUFFER_STATE b;
+ char *buf;
+ yy_size_t n;
+ int i;
+
+ /* Get memory for full buffer, including space for trailing EOB's. */
+ n = (yy_size_t) (_yybytes_len + 2);
+ buf = (char *) yyalloc( n );
+ if ( ! buf )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" );
+
+ for ( i = 0; i < _yybytes_len; ++i )
+ buf[i] = yybytes[i];
+
+ buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR;
+
+ b = yy_scan_buffer( buf, n );
+ if ( ! b )
+ YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" );
+
+ /* It's okay to grow etc. this buffer, and we should throw it
+ * away when we're done.
+ */
+ b->yy_is_our_buffer = 1;
+
+ return b;
+}
+/* %endif */
+
+#ifndef YY_EXIT_FAILURE
+#define YY_EXIT_FAILURE 2
+#endif
+
+/* %if-c-only */
+static void yynoreturn yy_fatal_error (const char* msg )
+{
+ fprintf( stderr, "%s\n", msg );
+ exit( YY_EXIT_FAILURE );
+}
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+
+/* Redefine yyless() so it works in section 3 code. */
+
+#undef yyless
+#define yyless(n) \
+ do \
+ { \
+ /* Undo effects of setting up yytext. */ \
+ int yyless_macro_arg = (n); \
+ YY_LESS_LINENO(yyless_macro_arg);\
+ yytext[yyleng] = (yy_hold_char); \
+ (yy_c_buf_p) = yytext + yyless_macro_arg; \
+ (yy_hold_char) = *(yy_c_buf_p); \
+ *(yy_c_buf_p) = '\0'; \
+ yyleng = yyless_macro_arg; \
+ } \
+ while ( 0 )
+
+/* Accessor methods (get/set functions) to struct members. */
+
+/* %if-c-only */
+/* %if-reentrant */
+/* %endif */
+
+/** Get the current line number.
+ *
+ */
+int yyget_lineno (void)
+{
+
+ return yylineno;
+}
+
+/** Get the input stream.
+ *
+ */
+FILE *yyget_in (void)
+{
+ return yyin;
+}
+
+/** Get the output stream.
+ *
+ */
+FILE *yyget_out (void)
+{
+ return yyout;
+}
+
+/** Get the length of the current token.
+ *
+ */
+int yyget_leng (void)
+{
+ return yyleng;
+}
+
+/** Get the current token.
+ *
+ */
+
+char *yyget_text (void)
+{
+ return yytext;
+}
+
+/* %if-reentrant */
+/* %endif */
+
+/** Set the current line number.
+ * @param _line_number line number
+ *
+ */
+void yyset_lineno (int _line_number )
+{
+
+ yylineno = _line_number;
+}
+
+/** Set the input stream. This does not discard the current
+ * input buffer.
+ * @param _in_str A readable stream.
+ *
+ * @see yy_switch_to_buffer
+ */
+void yyset_in (FILE * _in_str )
+{
+ yyin = _in_str ;
+}
+
+void yyset_out (FILE * _out_str )
+{
+ yyout = _out_str ;
+}
+
+int yyget_debug (void)
+{
+ return yy_flex_debug;
+}
+
+void yyset_debug (int _bdebug )
+{
+ yy_flex_debug = _bdebug ;
+}
+
+/* %endif */
+
+/* %if-reentrant */
+/* %if-bison-bridge */
+/* %endif */
+/* %endif if-c-only */
+
+/* %if-c-only */
+static int yy_init_globals (void)
+{
+ /* Initialization is the same as for the non-reentrant scanner.
+ * This function is called from yylex_destroy(), so don't allocate here.
+ */
+
+ (yy_buffer_stack) = NULL;
+ (yy_buffer_stack_top) = 0;
+ (yy_buffer_stack_max) = 0;
+ (yy_c_buf_p) = NULL;
+ (yy_init) = 0;
+ (yy_start) = 0;
+
+/* Defined in main.c */
+#ifdef YY_STDINIT
+ yyin = stdin;
+ yyout = stdout;
+#else
+ yyin = NULL;
+ yyout = NULL;
+#endif
+
+ /* For future reference: Set errno on error, since we are called by
+ * yylex_init()
+ */
+ return 0;
+}
+/* %endif */
+
+/* %if-c-only SNIP! this currently causes conflicts with the c++ scanner */
+/* yylex_destroy is for both reentrant and non-reentrant scanners. */
+int yylex_destroy (void)
+{
+
+ /* Pop the buffer stack, destroying each element. */
+ while(YY_CURRENT_BUFFER){
+ yy_delete_buffer( YY_CURRENT_BUFFER );
+ YY_CURRENT_BUFFER_LVALUE = NULL;
+ yypop_buffer_state();
+ }
+
+ /* Destroy the stack itself. */
+ yyfree((yy_buffer_stack) );
+ (yy_buffer_stack) = NULL;
+
+ /* Reset the globals. This is important in a non-reentrant scanner so the next time
+ * yylex() is called, initialization will occur. */
+ yy_init_globals( );
+
+/* %if-reentrant */
+/* %endif */
+ return 0;
+}
+/* %endif */
+
+/*
+ * Internal utility routines.
+ */
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char* s1, const char * s2, int n )
+{
+
+ int i;
+ for ( i = 0; i < n; ++i )
+ s1[i] = s2[i];
+}
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (const char * s )
+{
+ int n;
+ for ( n = 0; s[n]; ++n )
+ ;
+
+ return n;
+}
+#endif
+
+void *yyalloc (yy_size_t size )
+{
+ return malloc(size);
+}
+
+void *yyrealloc (void * ptr, yy_size_t size )
+{
+
+ /* The cast to (char *) in the following accommodates both
+ * implementations that use char* generic pointers, and those
+ * that use void* generic pointers. It works with the latter
+ * because both ANSI C and C++ allow castless assignment from
+ * any pointer type to void*, and deal with argument conversions
+ * as though doing an assignment.
+ */
+ return realloc(ptr, size);
+}
+
+void yyfree (void * ptr )
+{
+ free( (char *) ptr ); /* see yyrealloc() for (char *) cast */
+}
+
+/* %if-tables-serialization definitions */
+/* %define-yytables The name for this specific scanner's tables. */
+#define YYTABLES_NAME "yytables"
+/* %endif */
+
+/* %ok-for-header */
+
+#line 2275 "dhcp4_lexer.ll"
+
+
+using namespace isc::dhcp;
+
+void
+Parser4Context::scanStringBegin(const std::string& str, ParserType parser_type)
+{
+ start_token_flag = true;
+ start_token_value = parser_type;
+
+ file_ = "<string>";
+ sfile_ = 0;
+ loc_.initialize(&file_);
+ yy_flex_debug = trace_scanning_;
+ YY_BUFFER_STATE buffer;
+ buffer = parser4__scan_bytes(str.c_str(), str.size());
+ if (!buffer) {
+ fatal("cannot scan string");
+ /* fatal() throws an exception so this can't be reached */
+ }
+}
+
+void
+Parser4Context::scanFileBegin(FILE * f,
+ const std::string& filename,
+ ParserType parser_type)
+{
+ start_token_flag = true;
+ start_token_value = parser_type;
+
+ file_ = filename;
+ sfile_ = f;
+ loc_.initialize(&file_);
+ yy_flex_debug = trace_scanning_;
+ YY_BUFFER_STATE buffer;
+
+ /* See dhcp4_lexer.cc header for available definitions */
+ buffer = parser4__create_buffer(f, 65536 /*buffer size*/);
+ if (!buffer) {
+ fatal("cannot scan file " + filename);
+ }
+ parser4__switch_to_buffer(buffer);
+}
+
+void
+Parser4Context::scanEnd() {
+ if (sfile_)
+ fclose(sfile_);
+ sfile_ = 0;
+ static_cast<void>(parser4_lex_destroy());
+ /* Close files */
+ while (!sfiles_.empty()) {
+ FILE* f = sfiles_.back();
+ if (f) {
+ fclose(f);
+ }
+ sfiles_.pop_back();
+ }
+ /* Delete states */
+ while (!states_.empty()) {
+ parser4__delete_buffer(states_.back());
+ states_.pop_back();
+ }
+}
+
+void
+Parser4Context::includeFile(const std::string& filename) {
+ if (states_.size() > 10) {
+ fatal("Too many nested include.");
+ }
+
+ FILE* f = fopen(filename.c_str(), "r");
+ if (!f) {
+ fatal("Can't open include file " + filename);
+ }
+ if (sfile_) {
+ sfiles_.push_back(sfile_);
+ }
+ sfile_ = f;
+ states_.push_back(YY_CURRENT_BUFFER);
+ YY_BUFFER_STATE buffer;
+ buffer = parser4__create_buffer(f, 65536 /*buffer size*/);
+ if (!buffer) {
+ fatal( "Can't scan include file " + filename);
+ }
+ parser4__switch_to_buffer(buffer);
+ files_.push_back(file_);
+ file_ = filename;
+ locs_.push_back(loc_);
+ loc_.initialize(&file_);
+
+ BEGIN(INITIAL);
+}
+
+namespace {
+/** To avoid unused function error */
+class Dummy {
+ /* cppcheck-suppress unusedPrivateFunction */
+ void dummy() { yy_fatal_error("Fix me: how to disable its definition?"); }
+};
+}
+#endif /* !__clang_analyzer__ */
+
diff --git a/src/bin/dhcp4/dhcp4_lexer.ll b/src/bin/dhcp4/dhcp4_lexer.ll
new file mode 100644
index 0000000..a2d9da3
--- /dev/null
+++ b/src/bin/dhcp4/dhcp4_lexer.ll
@@ -0,0 +1,2375 @@
+/* Copyright (C) 2016-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/. */
+
+%{ /* -*- C++ -*- */
+
+/* Generated files do not make clang static analyser so happy */
+#ifndef __clang_analyzer__
+
+#include <cctype>
+#include <cerrno>
+#include <climits>
+#include <cstdlib>
+#include <string>
+#include <dhcp4/parser_context.h>
+#include <asiolink/io_address.h>
+#include <boost/lexical_cast.hpp>
+#include <exceptions/exceptions.h>
+
+/* Please avoid C++ style comments (// ... eol) as they break flex 2.6.2 */
+
+/* Work around an incompatibility in flex (at least versions
+ 2.5.31 through 2.5.33): it generates code that does
+ not conform to C89. See Debian bug 333231
+ <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=333231>. */
+# undef yywrap
+# define yywrap() 1
+
+namespace {
+
+bool start_token_flag = false;
+
+isc::dhcp::Parser4Context::ParserType start_token_value;
+unsigned int comment_start_line = 0;
+
+using namespace isc::dhcp;
+
+};
+
+/* To avoid the call to exit... oops! */
+#define YY_FATAL_ERROR(msg) isc::dhcp::Parser4Context::fatal(msg)
+%}
+
+/* noyywrap disables automatic rewinding for the next file to parse. Since we
+ always parse only a single string, there's no need to do any wraps. And
+ using yywrap requires linking with -lfl, which provides the default yywrap
+ implementation that always returns 1 anyway. */
+%option noyywrap
+
+/* nounput simplifies the lexer, by removing support for putting a character
+ back into the input stream. We never use such capability anyway. */
+%option nounput
+
+/* batch means that we'll never use the generated lexer interactively. */
+%option batch
+
+/* avoid to get static global variables to remain with C++. */
+/* in last resort %option reentrant */
+
+/* Enables debug mode. To see the debug messages, one needs to also set
+ yy_flex_debug to 1, then the debug messages will be printed on stderr. */
+%option debug
+
+/* I have no idea what this option does, except it was specified in the bison
+ examples and Postgres folks added it to remove gcc 4.3 warnings. Let's
+ be on the safe side and keep it. */
+%option noinput
+
+%x COMMENT
+%x DIR_ENTER DIR_INCLUDE DIR_EXIT
+
+/* These are not token expressions yet, just convenience expressions that
+ can be used during actual token definitions. Note some can match
+ incorrect inputs (e.g., IP addresses) which must be checked. */
+int \-?[0-9]+
+blank [ \t\r]
+
+UnicodeEscapeSequence u[0-9A-Fa-f]{4}
+JSONEscapeCharacter ["\\/bfnrt]
+JSONEscapeSequence {JSONEscapeCharacter}|{UnicodeEscapeSequence}
+JSONStandardCharacter [^\x00-\x1f"\\]
+JSONStringCharacter {JSONStandardCharacter}|\\{JSONEscapeSequence}
+JSONString \"{JSONStringCharacter}*\"
+
+/* for errors */
+
+BadUnicodeEscapeSequence u[0-9A-Fa-f]{0,3}[^0-9A-Fa-f"]
+BadJSONEscapeSequence [^"\\/bfnrtu]|{BadUnicodeEscapeSequence}
+ControlCharacter [\x00-\x1f]
+ControlCharacterFill [^"\\]|\\["\\/bfnrtu]
+
+%{
+/* This code run each time a pattern is matched. It updates the location
+ by moving it ahead by yyleng bytes. yyleng specifies the length of the
+ currently matched token. */
+#define YY_USER_ACTION driver.loc_.columns(yyleng);
+%}
+
+%%
+
+%{
+ /* This part of the code is copied over to the verbatim to the top
+ of the generated yylex function. Explanation:
+ http://www.gnu.org/software/bison/manual/html_node/Multiple-start_002dsymbols.html */
+
+ /* Code run each time yylex is called. */
+ driver.loc_.step();
+
+ if (start_token_flag) {
+ start_token_flag = false;
+ switch (start_token_value) {
+ case Parser4Context::PARSER_JSON:
+ default:
+ return isc::dhcp::Dhcp4Parser::make_TOPLEVEL_JSON(driver.loc_);
+ case Parser4Context::PARSER_DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_TOPLEVEL_DHCP4(driver.loc_);
+ case Parser4Context::SUBPARSER_DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_SUB_DHCP4(driver.loc_);
+ case Parser4Context::PARSER_INTERFACES:
+ return isc::dhcp::Dhcp4Parser::make_SUB_INTERFACES4(driver.loc_);
+ case Parser4Context::PARSER_SUBNET4:
+ return isc::dhcp::Dhcp4Parser::make_SUB_SUBNET4(driver.loc_);
+ case Parser4Context::PARSER_POOL4:
+ return isc::dhcp::Dhcp4Parser::make_SUB_POOL4(driver.loc_);
+ case Parser4Context::PARSER_HOST_RESERVATION:
+ return isc::dhcp::Dhcp4Parser::make_SUB_RESERVATION(driver.loc_);
+ case Parser4Context::PARSER_OPTION_DEFS:
+ return isc::dhcp::Dhcp4Parser::make_SUB_OPTION_DEFS(driver.loc_);
+ case Parser4Context::PARSER_OPTION_DEF:
+ return isc::dhcp::Dhcp4Parser::make_SUB_OPTION_DEF(driver.loc_);
+ case Parser4Context::PARSER_OPTION_DATA:
+ return isc::dhcp::Dhcp4Parser::make_SUB_OPTION_DATA(driver.loc_);
+ case Parser4Context::PARSER_HOOKS_LIBRARY:
+ return isc::dhcp::Dhcp4Parser::make_SUB_HOOKS_LIBRARY(driver.loc_);
+ case Parser4Context::PARSER_DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_SUB_DHCP_DDNS(driver.loc_);
+ case Parser4Context::PARSER_CONFIG_CONTROL:
+ return isc::dhcp::Dhcp4Parser::make_SUB_CONFIG_CONTROL(driver.loc_);
+ }
+ }
+%}
+
+#.* ;
+
+"//"(.*) ;
+
+"/*" {
+ BEGIN(COMMENT);
+ comment_start_line = driver.loc_.end.line;;
+}
+
+<COMMENT>"*/" BEGIN(INITIAL);
+<COMMENT>. ;
+<COMMENT><<EOF>> {
+ isc_throw(Dhcp4ParseError, "Comment not closed. (/* in line " << comment_start_line);
+}
+
+"<?" BEGIN(DIR_ENTER);
+<DIR_ENTER>"include" BEGIN(DIR_INCLUDE);
+<DIR_INCLUDE>\"([^\"\n])+\" {
+ /* Include directive. */
+
+ /* Extract the filename. */
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+
+ driver.includeFile(tmp);
+}
+<DIR_ENTER,DIR_INCLUDE,DIR_EXIT><<EOF>> {
+ isc_throw(Dhcp4ParseError, "Directive not closed.");
+}
+<DIR_EXIT>"?>" BEGIN(INITIAL);
+
+
+<*>{blank}+ {
+ /* Ok, we found a with space. Let's ignore it and update loc variable. */
+ driver.loc_.step();
+}
+
+<*>[\n]+ {
+ /* Newline found. Let's update the location and continue. */
+ driver.loc_.lines(yyleng);
+ driver.loc_.step();
+}
+
+
+\"Dhcp4\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::CONFIG:
+ return isc::dhcp::Dhcp4Parser::make_DHCP4(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("Dhcp4", driver.loc_);
+ }
+}
+
+\"interfaces-config\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_INTERFACES_CONFIG(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("interfaces-config", driver.loc_);
+ }
+}
+
+\"sanity-checks\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_SANITY_CHECKS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("sanity-checks", driver.loc_);
+ }
+}
+
+\"lease-checks\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SANITY_CHECKS:
+ return isc::dhcp::Dhcp4Parser::make_LEASE_CHECKS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("lease-checks", driver.loc_);
+ }
+}
+
+\"dhcp-socket-type\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::INTERFACES_CONFIG:
+ return isc::dhcp::Dhcp4Parser::make_DHCP_SOCKET_TYPE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("dhcp-socket-type", driver.loc_);
+ }
+}
+
+\"raw\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_SOCKET_TYPE:
+ return isc::dhcp::Dhcp4Parser::make_RAW(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("raw", driver.loc_);
+ }
+}
+
+\"udp\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_SOCKET_TYPE:
+ case isc::dhcp::Parser4Context::NCR_PROTOCOL:
+ return isc::dhcp::Dhcp4Parser::make_UDP(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("udp", driver.loc_);
+ }
+}
+
+\"outbound-interface\" {
+ switch(driver.ctx_) {
+ case Parser4Context::INTERFACES_CONFIG:
+ return isc::dhcp::Dhcp4Parser::make_OUTBOUND_INTERFACE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("outbound-interface", driver.loc_);
+ }
+}
+
+\"same-as-inbound\" {
+ switch(driver.ctx_) {
+ case Parser4Context::OUTBOUND_INTERFACE:
+ return Dhcp4Parser::make_SAME_AS_INBOUND(driver.loc_);
+ default:
+ return Dhcp4Parser::make_STRING("same-as-inbound", driver.loc_);
+ }
+}
+
+\"use-routing\" {
+ switch(driver.ctx_) {
+ case Parser4Context::OUTBOUND_INTERFACE:
+ return Dhcp4Parser::make_USE_ROUTING(driver.loc_);
+ default:
+ return Dhcp4Parser::make_STRING("use-routing", driver.loc_);
+ }
+}
+
+\"interfaces\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::INTERFACES_CONFIG:
+ return isc::dhcp::Dhcp4Parser::make_INTERFACES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("interfaces", driver.loc_);
+ }
+}
+
+\"re-detect\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::INTERFACES_CONFIG:
+ return isc::dhcp::Dhcp4Parser::make_RE_DETECT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("re-detect", driver.loc_);
+ }
+}
+
+\"service-sockets-require-all\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::INTERFACES_CONFIG:
+ return isc::dhcp::Dhcp4Parser::make_SERVICE_SOCKETS_REQUIRE_ALL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("service-sockets-require-all", driver.loc_);
+ }
+}
+
+\"service-sockets-retry-wait-time\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::INTERFACES_CONFIG:
+ return isc::dhcp::Dhcp4Parser::make_SERVICE_SOCKETS_RETRY_WAIT_TIME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("service-sockets-retry-wait-time", driver.loc_);
+ }
+}
+
+\"service-sockets-max-retries\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::INTERFACES_CONFIG:
+ return isc::dhcp::Dhcp4Parser::make_SERVICE_SOCKETS_MAX_RETRIES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("service-sockets-max-retries", driver.loc_);
+ }
+}
+
+\"lease-database\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_LEASE_DATABASE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("lease-database", driver.loc_);
+ }
+}
+
+\"hosts-database\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_HOSTS_DATABASE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("hosts-database", driver.loc_);
+ }
+}
+
+\"hosts-databases\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_HOSTS_DATABASES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("hosts-databases", driver.loc_);
+ }
+}
+
+\"config-control\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_CONFIG_CONTROL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("config-control", driver.loc_);
+ }
+}
+
+\"config-databases\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::CONFIG_CONTROL:
+ return isc::dhcp::Dhcp4Parser::make_CONFIG_DATABASES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("config-databases", driver.loc_);
+ }
+}
+
+\"config-fetch-wait-time\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::CONFIG_CONTROL:
+ return isc::dhcp::Dhcp4Parser::make_CONFIG_FETCH_WAIT_TIME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("config-fetch-wait-time", driver.loc_);
+ }
+}
+
+\"readonly\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_READONLY(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("readonly", driver.loc_);
+ }
+}
+
+\"type\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::OPTION_DEF:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_TYPE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("type", driver.loc_);
+ }
+}
+
+\"memfile\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DATABASE_TYPE:
+ return isc::dhcp::Dhcp4Parser::make_MEMFILE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("memfile", driver.loc_);
+ }
+}
+
+\"mysql\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DATABASE_TYPE:
+ return isc::dhcp::Dhcp4Parser::make_MYSQL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("mysql", driver.loc_);
+ }
+}
+
+\"postgresql\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DATABASE_TYPE:
+ return isc::dhcp::Dhcp4Parser::make_POSTGRESQL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("postgresql", driver.loc_);
+ }
+}
+
+\"user\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_USER(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("user", driver.loc_);
+ }
+}
+
+\"password\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_PASSWORD(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("password", driver.loc_);
+ }
+}
+
+\"host\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_HOST(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("host", driver.loc_);
+ }
+}
+
+\"port\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_PORT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("port", driver.loc_);
+ }
+}
+
+\"persist\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_PERSIST(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("persist", driver.loc_);
+ }
+}
+
+\"lfc-interval\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_LFC_INTERVAL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("lfc-interval", driver.loc_);
+ }
+}
+
+\"connect-timeout\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_CONNECT_TIMEOUT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("connect-timeout", driver.loc_);
+ }
+}
+
+\"reconnect-wait-time\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_RECONNECT_WAIT_TIME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("reconnect-wait-time", driver.loc_);
+ }
+}
+
+\"on-fail\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_ON_FAIL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("on-fail", driver.loc_);
+ }
+}
+
+\"stop-retry-exit\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DATABASE_ON_FAIL:
+ return isc::dhcp::Dhcp4Parser::make_STOP_RETRY_EXIT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("stop-retry-exit", driver.loc_);
+ }
+}
+
+\"serve-retry-exit\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DATABASE_ON_FAIL:
+ return isc::dhcp::Dhcp4Parser::make_SERVE_RETRY_EXIT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("serve-retry-exit", driver.loc_);
+ }
+}
+
+\"serve-retry-continue\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DATABASE_ON_FAIL:
+ return isc::dhcp::Dhcp4Parser::make_SERVE_RETRY_CONTINUE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("serve-retry-continue", driver.loc_);
+ }
+}
+
+\"max-reconnect-tries\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_MAX_RECONNECT_TRIES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("max-reconnect-tries", driver.loc_);
+ }
+}
+
+\"max-row-errors\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_MAX_ROW_ERRORS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("max-row-errors", driver.loc_);
+ }
+}
+
+\"trust-anchor\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_TRUST_ANCHOR(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("trust-anchor", driver.loc_);
+ }
+}
+
+\"cert-file\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_CERT_FILE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("cert-file", driver.loc_);
+ }
+}
+
+\"key-file\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_KEY_FILE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("key-file", driver.loc_);
+ }
+}
+
+\"cipher-list\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ return isc::dhcp::Dhcp4Parser::make_CIPHER_LIST(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("cipher-list", driver.loc_);
+ }
+}
+
+\"valid-lifetime\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_VALID_LIFETIME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("valid-lifetime", driver.loc_);
+ }
+}
+
+\"min-valid-lifetime\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_MIN_VALID_LIFETIME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("min-valid-lifetime", driver.loc_);
+ }
+}
+
+\"max-valid-lifetime\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_MAX_VALID_LIFETIME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("max-valid-lifetime", driver.loc_);
+ }
+}
+
+\"renew-timer\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_RENEW_TIMER(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("renew-timer", driver.loc_);
+ }
+}
+
+\"rebind-timer\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_REBIND_TIMER(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("rebind-timer", driver.loc_);
+ }
+}
+
+\"decline-probation-period\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_DECLINE_PROBATION_PERIOD(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("decline-probation-period", driver.loc_);
+ }
+}
+
+\"server-tag\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_SERVER_TAG(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("server-tag", driver.loc_);
+ }
+}
+
+\"parked-packet-limit\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_PARKED_PACKET_LIMIT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("parked-packet-limit", driver.loc_);
+ }
+}
+
+\"statistic-default-sample-count\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_STATISTIC_DEFAULT_SAMPLE_COUNT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("statistic-default-sample-count", driver.loc_);
+ }
+}
+
+\"statistic-default-sample-age\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_STATISTIC_DEFAULT_SAMPLE_AGE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("statistic-default-sample-age", driver.loc_);
+ }
+}
+
+\"ddns-send-updates\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_DDNS_SEND_UPDATES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ddns-send-updates", driver.loc_);
+ }
+}
+
+\"ddns-override-no-update\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_DDNS_OVERRIDE_NO_UPDATE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ddns-override-no-update", driver.loc_);
+ }
+}
+
+\"ddns-override-client-update\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_DDNS_OVERRIDE_CLIENT_UPDATE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ddns-override-client-update", driver.loc_);
+ }
+}
+
+\"ddns-replace-client-name\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_DDNS_REPLACE_CLIENT_NAME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ddns-replace-client-name", driver.loc_);
+ }
+}
+
+\"ddns-generated-prefix\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_DDNS_GENERATED_PREFIX(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ddns-generated-prefix", driver.loc_);
+ }
+}
+
+\"ddns-qualifying-suffix\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_DDNS_QUALIFYING_SUFFIX(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ddns-qualifying-suffix", driver.loc_);
+ }
+}
+
+\"ddns-update-on-renew\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_DDNS_UPDATE_ON_RENEW(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ddns-update-on-renew", driver.loc_);
+ }
+}
+
+\"ddns-use-conflict-resolution\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_DDNS_USE_CONFLICT_RESOLUTION(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ddns-use-conflict-resolution", driver.loc_);
+ }
+}
+
+\"subnet4\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_SUBNET4(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("subnet4", driver.loc_);
+ }
+}
+
+\"store-extended-info\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_STORE_EXTENDED_INFO(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("store-extended-info", driver.loc_);
+ }
+}
+
+\"shared-networks\" {
+ switch (driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_SHARED_NETWORKS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("shared-networks", driver.loc_);
+ }
+}
+
+\"option-def\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_OPTION_DEF(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("option-def", driver.loc_);
+ }
+}
+
+\"option-data\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::POOLS:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_OPTION_DATA(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("option-data", driver.loc_);
+ }
+}
+
+\"name\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LEASE_DATABASE:
+ case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+ case isc::dhcp::Parser4Context::CONFIG_DATABASE:
+ case isc::dhcp::Parser4Context::OPTION_DEF:
+ case isc::dhcp::Parser4Context::OPTION_DATA:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::LOGGERS:
+ return isc::dhcp::Dhcp4Parser::make_NAME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("name", driver.loc_);
+ }
+}
+
+\"data\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OPTION_DATA:
+ return isc::dhcp::Dhcp4Parser::make_DATA(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("data", driver.loc_);
+ }
+}
+
+\"always-send\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OPTION_DATA:
+ return isc::dhcp::Dhcp4Parser::make_ALWAYS_SEND(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("always-send", driver.loc_);
+ }
+}
+
+\"pools\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ return isc::dhcp::Dhcp4Parser::make_POOLS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("pools", driver.loc_);
+ }
+}
+
+\"pool\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::POOLS:
+ return isc::dhcp::Dhcp4Parser::make_POOL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("pool", driver.loc_);
+ }
+}
+
+\"user-context\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::INTERFACES_CONFIG:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::POOLS:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::OPTION_DEF:
+ case isc::dhcp::Parser4Context::OPTION_DATA:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ case isc::dhcp::Parser4Context::CONTROL_SOCKET:
+ case isc::dhcp::Parser4Context::DHCP_QUEUE_CONTROL:
+ case isc::dhcp::Parser4Context::DHCP_MULTI_THREADING:
+ case isc::dhcp::Parser4Context::LOGGERS:
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_USER_CONTEXT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("user-context", driver.loc_);
+ }
+}
+
+\"comment\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::INTERFACES_CONFIG:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::POOLS:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::OPTION_DEF:
+ case isc::dhcp::Parser4Context::OPTION_DATA:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ case isc::dhcp::Parser4Context::CONTROL_SOCKET:
+ case isc::dhcp::Parser4Context::DHCP_QUEUE_CONTROL:
+ case isc::dhcp::Parser4Context::DHCP_MULTI_THREADING:
+ case isc::dhcp::Parser4Context::LOGGERS:
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_COMMENT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("comment", driver.loc_);
+ }
+}
+
+\"subnet\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ return isc::dhcp::Dhcp4Parser::make_SUBNET(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("subnet", driver.loc_);
+ }
+}
+
+\"interface\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_INTERFACE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("interface", driver.loc_);
+ }
+}
+
+\"id\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ return isc::dhcp::Dhcp4Parser::make_ID(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("id", driver.loc_);
+ }
+}
+
+\"reservation-mode\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_RESERVATION_MODE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("reservation-mode", driver.loc_);
+ }
+}
+
+\"disabled\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::RESERVATION_MODE:
+ return isc::dhcp::Dhcp4Parser::make_DISABLED(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("disabled", driver.loc_);
+ }
+}
+
+\"off\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::RESERVATION_MODE:
+ return isc::dhcp::Dhcp4Parser::make_DISABLED(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("off", driver.loc_);
+ }
+}
+
+\"out-of-pool\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::RESERVATION_MODE:
+ return isc::dhcp::Dhcp4Parser::make_OUT_OF_POOL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("out-of-pool", driver.loc_);
+ }
+}
+
+\"global\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::RESERVATION_MODE:
+ return isc::dhcp::Dhcp4Parser::make_GLOBAL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("global", driver.loc_);
+ }
+}
+
+\"all\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::RESERVATION_MODE:
+ return isc::dhcp::Dhcp4Parser::make_ALL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("all", driver.loc_);
+ }
+}
+
+\"reservations-global\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_RESERVATIONS_GLOBAL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("reservations-global", driver.loc_);
+ }
+}
+
+\"reservations-in-subnet\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_RESERVATIONS_IN_SUBNET(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("reservations-in-subnet", driver.loc_);
+ }
+}
+
+\"reservations-out-of-pool\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_RESERVATIONS_OUT_OF_POOL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("reservations-out-of-pool", driver.loc_);
+ }
+}
+
+\"code\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OPTION_DEF:
+ case isc::dhcp::Parser4Context::OPTION_DATA:
+ return isc::dhcp::Dhcp4Parser::make_CODE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("code", driver.loc_);
+ }
+}
+
+\"host-reservation-identifiers\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_HOST_RESERVATION_IDENTIFIERS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("host-reservation-identifiers", driver.loc_);
+ }
+}
+
+\"calculate-tee-times\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_CALCULATE_TEE_TIMES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("calculate-tee-times", driver.loc_);
+ }
+}
+
+\"t1-percent\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_T1_PERCENT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("t1-percent", driver.loc_);
+ }
+}
+
+\"t2-percent\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_T2_PERCENT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("t2-percent", driver.loc_);
+ }
+}
+
+\"cache-threshold\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_CACHE_THRESHOLD(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("cache-threshold", driver.loc_);
+ }
+}
+
+\"cache-max-age\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_CACHE_MAX_AGE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("cache-max-age", driver.loc_);
+ }
+}
+
+
+\"loggers\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_LOGGERS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("loggers", driver.loc_);
+ }
+}
+
+\"output_options\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LOGGERS:
+ return isc::dhcp::Dhcp4Parser::make_OUTPUT_OPTIONS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("output_options", driver.loc_);
+ }
+}
+
+\"output\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OUTPUT_OPTIONS:
+ return isc::dhcp::Dhcp4Parser::make_OUTPUT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("output", driver.loc_);
+ }
+}
+
+\"debuglevel\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LOGGERS:
+ return isc::dhcp::Dhcp4Parser::make_DEBUGLEVEL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("debuglevel", driver.loc_);
+ }
+}
+
+\"flush\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OUTPUT_OPTIONS:
+ return isc::dhcp::Dhcp4Parser::make_FLUSH(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("flush", driver.loc_);
+ }
+}
+
+\"maxsize\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OUTPUT_OPTIONS:
+ return isc::dhcp::Dhcp4Parser::make_MAXSIZE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("maxsize", driver.loc_);
+ }
+}
+
+\"maxver\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OUTPUT_OPTIONS:
+ return isc::dhcp::Dhcp4Parser::make_MAXVER(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("maxver", driver.loc_);
+ }
+}
+
+\"pattern\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OUTPUT_OPTIONS:
+ return isc::dhcp::Dhcp4Parser::make_PATTERN(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("pattern", driver.loc_);
+ }
+}
+
+\"severity\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::LOGGERS:
+ return isc::dhcp::Dhcp4Parser::make_SEVERITY(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("severity", driver.loc_);
+ }
+}
+
+\"client-classes\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ return isc::dhcp::Dhcp4Parser::make_CLIENT_CLASSES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("client-classes", driver.loc_);
+ }
+}
+
+\"require-client-classes\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::POOLS:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_REQUIRE_CLIENT_CLASSES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("require-client-classes", driver.loc_);
+ }
+}
+
+\"client-class\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::POOLS:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_CLIENT_CLASS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("client-class", driver.loc_);
+ }
+}
+
+\"test\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_TEST(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("test", driver.loc_);
+ }
+}
+
+\"only-if-required\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_ONLY_IF_REQUIRED(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("only-if-required", driver.loc_);
+ }
+}
+
+\"reservations\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ return isc::dhcp::Dhcp4Parser::make_RESERVATIONS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("reservations", driver.loc_);
+ }
+}
+
+\"duid\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::HOST_RESERVATION_IDENTIFIERS:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ return isc::dhcp::Dhcp4Parser::make_DUID(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("duid", driver.loc_);
+ }
+}
+
+\"hw-address\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::HOST_RESERVATION_IDENTIFIERS:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ return isc::dhcp::Dhcp4Parser::make_HW_ADDRESS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("hw-address", driver.loc_);
+ }
+}
+
+\"client-id\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::HOST_RESERVATION_IDENTIFIERS:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ return isc::dhcp::Dhcp4Parser::make_CLIENT_ID(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("client-id", driver.loc_);
+ }
+}
+
+\"circuit-id\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::HOST_RESERVATION_IDENTIFIERS:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ return isc::dhcp::Dhcp4Parser::make_CIRCUIT_ID(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("circuit-id", driver.loc_);
+ }
+}
+
+\"flex-id\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::HOST_RESERVATION_IDENTIFIERS:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ return isc::dhcp::Dhcp4Parser::make_FLEX_ID(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("flex-id", driver.loc_);
+ }
+}
+
+\"hostname\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ return isc::dhcp::Dhcp4Parser::make_HOSTNAME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("hostname", driver.loc_);
+ }
+}
+
+\"space\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OPTION_DEF:
+ case isc::dhcp::Parser4Context::OPTION_DATA:
+ return isc::dhcp::Dhcp4Parser::make_SPACE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("space", driver.loc_);
+ }
+}
+
+\"csv-format\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OPTION_DATA:
+ return isc::dhcp::Dhcp4Parser::make_CSV_FORMAT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("csv-format", driver.loc_);
+ }
+}
+
+\"record-types\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OPTION_DEF:
+ return isc::dhcp::Dhcp4Parser::make_RECORD_TYPES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("record-types", driver.loc_);
+ }
+}
+
+\"encapsulate\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OPTION_DEF:
+ return isc::dhcp::Dhcp4Parser::make_ENCAPSULATE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("encapsulate", driver.loc_);
+ }
+}
+
+\"array\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::OPTION_DEF:
+ return isc::dhcp::Dhcp4Parser::make_ARRAY(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("array", driver.loc_);
+ }
+}
+
+\"relay\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_RELAY(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("relay", driver.loc_);
+ }
+}
+
+\"ip-address\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::RELAY:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ return isc::dhcp::Dhcp4Parser::make_IP_ADDRESS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ip-address", driver.loc_);
+ }
+}
+
+\"ip-addresses\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::RELAY:
+ return isc::dhcp::Dhcp4Parser::make_IP_ADDRESSES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ip-addresses", driver.loc_);
+ }
+}
+
+\"hooks-libraries\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_HOOKS_LIBRARIES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("hooks-libraries", driver.loc_);
+ }
+}
+
+
+\"parameters\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::HOOKS_LIBRARIES:
+ return isc::dhcp::Dhcp4Parser::make_PARAMETERS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("parameters", driver.loc_);
+ }
+}
+
+\"library\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::HOOKS_LIBRARIES:
+ return isc::dhcp::Dhcp4Parser::make_LIBRARY(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("library", driver.loc_);
+ }
+}
+
+\"expired-leases-processing\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_EXPIRED_LEASES_PROCESSING(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("expired-leases-processing", driver.loc_);
+ }
+}
+
+\"reclaim-timer-wait-time\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::EXPIRED_LEASES_PROCESSING:
+ return isc::dhcp::Dhcp4Parser::make_RECLAIM_TIMER_WAIT_TIME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("reclaim-timer-wait-time", driver.loc_);
+ }
+}
+
+\"flush-reclaimed-timer-wait-time\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::EXPIRED_LEASES_PROCESSING:
+ return isc::dhcp::Dhcp4Parser::make_FLUSH_RECLAIMED_TIMER_WAIT_TIME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("flush-reclaimed-timer-wait-time", driver.loc_);
+ }
+}
+
+\"hold-reclaimed-time\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::EXPIRED_LEASES_PROCESSING:
+ return isc::dhcp::Dhcp4Parser::make_HOLD_RECLAIMED_TIME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("hold-reclaimed-time", driver.loc_);
+ }
+}
+
+\"max-reclaim-leases\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::EXPIRED_LEASES_PROCESSING:
+ return isc::dhcp::Dhcp4Parser::make_MAX_RECLAIM_LEASES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("max-reclaim-leases", driver.loc_);
+ }
+}
+
+\"max-reclaim-time\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::EXPIRED_LEASES_PROCESSING:
+ return isc::dhcp::Dhcp4Parser::make_MAX_RECLAIM_TIME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("max-reclaim-time", driver.loc_);
+ }
+}
+
+\"unwarned-reclaim-cycles\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::EXPIRED_LEASES_PROCESSING:
+ return isc::dhcp::Dhcp4Parser::make_UNWARNED_RECLAIM_CYCLES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("unwarned-reclaim-cycles", driver.loc_);
+ }
+}
+
+\"dhcp4o6-port\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_DHCP4O6_PORT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("dhcp4o6-port", driver.loc_);
+ }
+}
+
+\"multi-threading\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_DHCP_MULTI_THREADING(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("multi-threading", driver.loc_);
+ }
+}
+
+\"enable-multi-threading\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_MULTI_THREADING:
+ return isc::dhcp::Dhcp4Parser::make_ENABLE_MULTI_THREADING(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("enable-multi-threading", driver.loc_);
+ }
+}
+
+\"thread-pool-size\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_MULTI_THREADING:
+ return isc::dhcp::Dhcp4Parser::make_THREAD_POOL_SIZE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("thread-pool-size", driver.loc_);
+ }
+}
+
+\"packet-queue-size\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_MULTI_THREADING:
+ return isc::dhcp::Dhcp4Parser::make_PACKET_QUEUE_SIZE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("packet-queue-size", driver.loc_);
+ }
+}
+
+\"control-socket\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_CONTROL_SOCKET(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("control-socket", driver.loc_);
+ }
+}
+
+\"socket-type\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::CONTROL_SOCKET:
+ return isc::dhcp::Dhcp4Parser::make_SOCKET_TYPE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("socket-type", driver.loc_);
+ }
+}
+
+\"socket-name\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::CONTROL_SOCKET:
+ return isc::dhcp::Dhcp4Parser::make_SOCKET_NAME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("socket-name", driver.loc_);
+ }
+}
+
+\"dhcp-queue-control\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_DHCP_QUEUE_CONTROL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("dhcp-queue-control", driver.loc_);
+ }
+}
+
+\"enable-queue\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_QUEUE_CONTROL:
+ return isc::dhcp::Dhcp4Parser::make_ENABLE_QUEUE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("enable-queue", driver.loc_);
+ }
+}
+
+\"queue-type\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_QUEUE_CONTROL:
+ return isc::dhcp::Dhcp4Parser::make_QUEUE_TYPE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("queue-type", driver.loc_);
+ }
+}
+
+\"capacity\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_QUEUE_CONTROL:
+ return isc::dhcp::Dhcp4Parser::make_CAPACITY(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("capacity", driver.loc_);
+ }
+}
+
+\"dhcp-ddns\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_DHCP_DDNS(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("dhcp-ddns", driver.loc_);
+ }
+}
+
+\"enable-updates\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_ENABLE_UPDATES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("enable-updates", driver.loc_);
+ }
+}
+
+\"qualifying-suffix\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_QUALIFYING_SUFFIX(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("qualifying-suffix", driver.loc_);
+ }
+}
+
+\"server-ip\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_SERVER_IP(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("server-ip", driver.loc_);
+ }
+}
+
+\"server-port\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_SERVER_PORT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("server-port", driver.loc_);
+ }
+}
+
+\"sender-ip\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_SENDER_IP(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("sender-ip", driver.loc_);
+ }
+}
+
+\"sender-port\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_SENDER_PORT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("sender-port", driver.loc_);
+ }
+}
+
+\"max-queue-size\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_MAX_QUEUE_SIZE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("max-queue-size", driver.loc_);
+ }
+}
+
+\"ncr-protocol\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_NCR_PROTOCOL(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ncr-protocol", driver.loc_);
+ }
+}
+
+\"ncr-format\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_NCR_FORMAT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ncr-format", driver.loc_);
+ }
+}
+
+\"override-no-update\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_OVERRIDE_NO_UPDATE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("override-no-update", driver.loc_);
+ }
+}
+
+\"override-client-update\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_OVERRIDE_CLIENT_UPDATE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("override-client-update", driver.loc_);
+ }
+}
+
+\"replace-client-name\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_REPLACE_CLIENT_NAME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("replace-client-name", driver.loc_);
+ }
+}
+
+\"generated-prefix\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ return isc::dhcp::Dhcp4Parser::make_GENERATED_PREFIX(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("generated-prefix", driver.loc_);
+ }
+}
+
+\"hostname-char-set\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_HOSTNAME_CHAR_SET(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("hostname-char-set", driver.loc_);
+ }
+}
+
+\"hostname-char-replacement\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::DHCP_DDNS:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_HOSTNAME_CHAR_REPLACEMENT(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("hostname-char-replacement", driver.loc_);
+ }
+}
+
+(?i:\"UDP\") {
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::dhcp::Parser4Context::NCR_PROTOCOL) {
+ return isc::dhcp::Dhcp4Parser::make_UDP(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::dhcp::Dhcp4Parser::make_STRING(tmp, driver.loc_);
+}
+
+(?i:\"TCP\") {
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::dhcp::Parser4Context::NCR_PROTOCOL) {
+ return isc::dhcp::Dhcp4Parser::make_TCP(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::dhcp::Dhcp4Parser::make_STRING(tmp, driver.loc_);
+}
+
+(?i:\"JSON\") {
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::dhcp::Parser4Context::NCR_FORMAT) {
+ return isc::dhcp::Dhcp4Parser::make_JSON(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::dhcp::Dhcp4Parser::make_STRING(tmp, driver.loc_);
+}
+
+(?i:\"when-present\") {
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::dhcp::Parser4Context::REPLACE_CLIENT_NAME) {
+ return isc::dhcp::Dhcp4Parser::make_WHEN_PRESENT(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::dhcp::Dhcp4Parser::make_STRING(tmp, driver.loc_);
+}
+
+(?i:\"true\") {
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::dhcp::Parser4Context::REPLACE_CLIENT_NAME) {
+ return isc::dhcp::Dhcp4Parser::make_WHEN_PRESENT(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::dhcp::Dhcp4Parser::make_STRING(tmp, driver.loc_);
+}
+
+(?i:\"never\") {
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::dhcp::Parser4Context::REPLACE_CLIENT_NAME) {
+ return isc::dhcp::Dhcp4Parser::make_NEVER(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::dhcp::Dhcp4Parser::make_STRING(tmp, driver.loc_);
+}
+
+(?i:\"false\") {
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::dhcp::Parser4Context::REPLACE_CLIENT_NAME) {
+ return isc::dhcp::Dhcp4Parser::make_NEVER(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::dhcp::Dhcp4Parser::make_STRING(tmp, driver.loc_);
+}
+
+(?i:\"always\") {
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::dhcp::Parser4Context::REPLACE_CLIENT_NAME) {
+ return isc::dhcp::Dhcp4Parser::make_ALWAYS(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::dhcp::Dhcp4Parser::make_STRING(tmp, driver.loc_);
+}
+
+(?i:\"when-not-present\") {
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::dhcp::Parser4Context::REPLACE_CLIENT_NAME) {
+ return isc::dhcp::Dhcp4Parser::make_WHEN_NOT_PRESENT(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::dhcp::Dhcp4Parser::make_STRING(tmp, driver.loc_);
+}
+
+\"4o6-interface\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ return isc::dhcp::Dhcp4Parser::make_SUBNET_4O6_INTERFACE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("4o6-interface", driver.loc_);
+ }
+}
+
+\"4o6-interface-id\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ return isc::dhcp::Dhcp4Parser::make_SUBNET_4O6_INTERFACE_ID(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("4o6-interface-id", driver.loc_);
+ }
+}
+
+\"4o6-subnet\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ return isc::dhcp::Dhcp4Parser::make_SUBNET_4O6_SUBNET(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("4o6-subnet", driver.loc_);
+ }
+}
+
+\"echo-client-id\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_ECHO_CLIENT_ID(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("echo-client-id", driver.loc_);
+ }
+}
+
+\"match-client-id\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_MATCH_CLIENT_ID(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("match-client-id", driver.loc_);
+ }
+}
+
+\"authoritative\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_AUTHORITATIVE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("authoritative", driver.loc_);
+ }
+}
+
+\"next-server\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_NEXT_SERVER(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("next-server", driver.loc_);
+ }
+}
+
+\"server-hostname\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_SERVER_HOSTNAME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("server-hostname", driver.loc_);
+ }
+}
+
+\"boot-file-name\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ case isc::dhcp::Parser4Context::RESERVATIONS:
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_BOOT_FILE_NAME(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("boot-file-name", driver.loc_);
+ }
+}
+
+\"early-global-reservations-lookup\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_EARLY_GLOBAL_RESERVATIONS_LOOKUP(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("early-global-reservations-lookup", driver.loc_);
+ }
+}
+
+\"ip-reservations-unique\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_IP_RESERVATIONS_UNIQUE(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("ip-reservations-unique", driver.loc_);
+ }
+}
+
+\"reservations-lookup-first\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_RESERVATIONS_LOOKUP_FIRST(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("reservations-lookup-first", driver.loc_);
+ }
+}
+
+\"compatibility\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::DHCP4:
+ return isc::dhcp::Dhcp4Parser::make_COMPATIBILITY(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("compatibility", driver.loc_);
+ }
+}
+
+\"lenient-option-parsing\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::COMPATIBILITY:
+ return isc::dhcp::Dhcp4Parser::make_LENIENT_OPTION_PARSING(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("lenient-option-parsing", driver.loc_);
+ }
+}
+
+{JSONString} {
+ /* A string has been matched. It contains the actual string and single quotes.
+ We need to get those quotes out of the way and just use its content, e.g.
+ for 'foo' we should get foo */
+ std::string raw(yytext+1);
+ size_t len = raw.size() - 1;
+ raw.resize(len);
+ std::string decoded;
+ decoded.reserve(len);
+ for (size_t pos = 0; pos < len; ++pos) {
+ int b = 0;
+ char c = raw[pos];
+ switch (c) {
+ case '"':
+ /* impossible condition */
+ driver.error(driver.loc_, "Bad quote in \"" + raw + "\"");
+ break;
+ case '\\':
+ ++pos;
+ if (pos >= len) {
+ /* impossible condition */
+ driver.error(driver.loc_, "Overflow escape in \"" + raw + "\"");
+ }
+ c = raw[pos];
+ switch (c) {
+ case '"':
+ case '\\':
+ case '/':
+ decoded.push_back(c);
+ break;
+ case 'b':
+ decoded.push_back('\b');
+ break;
+ case 'f':
+ decoded.push_back('\f');
+ break;
+ case 'n':
+ decoded.push_back('\n');
+ break;
+ case 'r':
+ decoded.push_back('\r');
+ break;
+ case 't':
+ decoded.push_back('\t');
+ break;
+ case 'u':
+ /* support only \u0000 to \u00ff */
+ ++pos;
+ if (pos + 4 > len) {
+ /* impossible condition */
+ driver.error(driver.loc_,
+ "Overflow unicode escape in \"" + raw + "\"");
+ }
+ if ((raw[pos] != '0') || (raw[pos + 1] != '0')) {
+ driver.error(driver.loc_,
+ "Unsupported unicode escape in \"" + raw + "\"",
+ pos + 1);
+ }
+ pos += 2;
+ c = raw[pos];
+ if ((c >= '0') && (c <= '9')) {
+ b = (c - '0') << 4;
+ } else if ((c >= 'A') && (c <= 'F')) {
+ b = (c - 'A' + 10) << 4;
+ } else if ((c >= 'a') && (c <= 'f')) {
+ b = (c - 'a' + 10) << 4;
+ } else {
+ /* impossible condition */
+ driver.error(driver.loc_, "Not hexadecimal in unicode escape in \"" + raw + "\"");
+ }
+ pos++;
+ c = raw[pos];
+ if ((c >= '0') && (c <= '9')) {
+ b |= c - '0';
+ } else if ((c >= 'A') && (c <= 'F')) {
+ b |= c - 'A' + 10;
+ } else if ((c >= 'a') && (c <= 'f')) {
+ b |= c - 'a' + 10;
+ } else {
+ /* impossible condition */
+ driver.error(driver.loc_, "Not hexadecimal in unicode escape in \"" + raw + "\"");
+ }
+ decoded.push_back(static_cast<char>(b & 0xff));
+ break;
+ default:
+ /* impossible condition */
+ driver.error(driver.loc_, "Bad escape in \"" + raw + "\"");
+ }
+ break;
+ default:
+ if ((c >= 0) && (c < 0x20)) {
+ /* impossible condition */
+ driver.error(driver.loc_, "Invalid control in \"" + raw + "\"");
+ }
+ decoded.push_back(c);
+ }
+ }
+
+ return isc::dhcp::Dhcp4Parser::make_STRING(decoded, driver.loc_);
+}
+
+\"{JSONStringCharacter}*{ControlCharacter}{ControlCharacterFill}*\" {
+ /* Bad string with a forbidden control character inside */
+ std::string raw(yytext+1);
+ size_t len = raw.size() - 1;
+ size_t pos = 0;
+ for (; pos < len; ++pos) {
+ char c = raw[pos];
+ if ((c >= 0) && (c < 0x20)) {
+ break;
+ }
+ }
+ driver.error(driver.loc_,
+ "Invalid control in " + std::string(yytext),
+ pos + 1);
+}
+
+\"{JSONStringCharacter}*\\{BadJSONEscapeSequence}[^"]*\" {
+ /* Bad string with a bad escape inside */
+ std::string raw(yytext+1);
+ size_t len = raw.size() - 1;
+ size_t pos = 0;
+ bool found = false;
+ for (; pos < len; ++pos) {
+ if (found) {
+ break;
+ }
+ char c = raw[pos];
+ if (c == '\\') {
+ ++pos;
+ c = raw[pos];
+ switch (c) {
+ case '"':
+ case '\\':
+ case '/':
+ case 'b':
+ case 'f':
+ case 'n':
+ case 'r':
+ case 't':
+ break;
+ case 'u':
+ if ((pos + 4 > len) ||
+ !std::isxdigit(raw[pos + 1]) ||
+ !std::isxdigit(raw[pos + 2]) ||
+ !std::isxdigit(raw[pos + 3]) ||
+ !std::isxdigit(raw[pos + 4])) {
+ found = true;
+ }
+ break;
+ default:
+ found = true;
+ break;
+ }
+ }
+ }
+ /* The rule stops on the first " including on \" so add ... in this case */
+ std::string trailer = "";
+ if (raw[len - 1] == '\\') {
+ trailer = "...";
+ }
+ driver.error(driver.loc_,
+ "Bad escape in " + std::string(yytext) + trailer,
+ pos);
+}
+
+\"{JSONStringCharacter}*\\\" {
+ /* Bad string with an open escape at the end */
+ std::string raw(yytext+1);
+ driver.error(driver.loc_,
+ "Overflow escape in " + std::string(yytext),
+ raw.size() + 1);
+}
+
+\"{JSONStringCharacter}*\\u[0-9A-Fa-f]{0,3}\" {
+ /* Bad string with an open unicode escape at the end */
+ std::string raw(yytext+1);
+ size_t pos = raw.size() - 1;
+ for (; pos > 0; --pos) {
+ char c = raw[pos];
+ if (c == 'u') {
+ break;
+ }
+ }
+ driver.error(driver.loc_,
+ "Overflow unicode escape in " + std::string(yytext),
+ pos + 1);
+}
+
+"[" { return isc::dhcp::Dhcp4Parser::make_LSQUARE_BRACKET(driver.loc_); }
+"]" { return isc::dhcp::Dhcp4Parser::make_RSQUARE_BRACKET(driver.loc_); }
+"{" { return isc::dhcp::Dhcp4Parser::make_LCURLY_BRACKET(driver.loc_); }
+"}" { return isc::dhcp::Dhcp4Parser::make_RCURLY_BRACKET(driver.loc_); }
+"," { return isc::dhcp::Dhcp4Parser::make_COMMA(driver.loc_); }
+":" { return isc::dhcp::Dhcp4Parser::make_COLON(driver.loc_); }
+
+{int} {
+ /* An integer was found. */
+ std::string tmp(yytext);
+ int64_t integer = 0;
+ try {
+ /* In substring we want to use negative values (e.g. -1).
+ In enterprise-id we need to use values up to 0xffffffff.
+ To cover both of those use cases, we need at least
+ int64_t. */
+ integer = boost::lexical_cast<int64_t>(tmp);
+ } catch (const boost::bad_lexical_cast &) {
+ driver.error(driver.loc_, "Failed to convert " + tmp + " to an integer.");
+ }
+
+ /* The parser needs the string form as double conversion is no lossless */
+ return isc::dhcp::Dhcp4Parser::make_INTEGER(integer, driver.loc_);
+}
+
+[-+]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)? {
+ /* A floating point was found. */
+ std::string tmp(yytext);
+ double fp = 0.0;
+ try {
+ fp = boost::lexical_cast<double>(tmp);
+ } catch (const boost::bad_lexical_cast &) {
+ driver.error(driver.loc_, "Failed to convert " + tmp + " to a floating point.");
+ }
+
+ return isc::dhcp::Dhcp4Parser::make_FLOAT(fp, driver.loc_);
+}
+
+true|false {
+ string tmp(yytext);
+ return isc::dhcp::Dhcp4Parser::make_BOOLEAN(tmp == "true", driver.loc_);
+}
+
+null {
+ return isc::dhcp::Dhcp4Parser::make_NULL_TYPE(driver.loc_);
+}
+
+(?i:true) driver.error (driver.loc_, "JSON true reserved keyword is lower case only");
+
+(?i:false) driver.error (driver.loc_, "JSON false reserved keyword is lower case only");
+
+(?i:null) driver.error (driver.loc_, "JSON null reserved keyword is lower case only");
+
+<*>. driver.error (driver.loc_, "Invalid character: " + std::string(yytext));
+
+<<EOF>> {
+ if (driver.states_.empty()) {
+ return isc::dhcp::Dhcp4Parser::make_END(driver.loc_);
+ }
+ driver.loc_ = driver.locs_.back();
+ driver.locs_.pop_back();
+ driver.file_ = driver.files_.back();
+ driver.files_.pop_back();
+ if (driver.sfile_) {
+ fclose(driver.sfile_);
+ driver.sfile_ = 0;
+ }
+ if (!driver.sfiles_.empty()) {
+ driver.sfile_ = driver.sfiles_.back();
+ driver.sfiles_.pop_back();
+ }
+ parser4__delete_buffer(YY_CURRENT_BUFFER);
+ parser4__switch_to_buffer(driver.states_.back());
+ driver.states_.pop_back();
+
+ BEGIN(DIR_EXIT);
+}
+
+%%
+
+using namespace isc::dhcp;
+
+void
+Parser4Context::scanStringBegin(const std::string& str, ParserType parser_type)
+{
+ start_token_flag = true;
+ start_token_value = parser_type;
+
+ file_ = "<string>";
+ sfile_ = 0;
+ loc_.initialize(&file_);
+ yy_flex_debug = trace_scanning_;
+ YY_BUFFER_STATE buffer;
+ buffer = parser4__scan_bytes(str.c_str(), str.size());
+ if (!buffer) {
+ fatal("cannot scan string");
+ /* fatal() throws an exception so this can't be reached */
+ }
+}
+
+void
+Parser4Context::scanFileBegin(FILE * f,
+ const std::string& filename,
+ ParserType parser_type)
+{
+ start_token_flag = true;
+ start_token_value = parser_type;
+
+ file_ = filename;
+ sfile_ = f;
+ loc_.initialize(&file_);
+ yy_flex_debug = trace_scanning_;
+ YY_BUFFER_STATE buffer;
+
+ /* See dhcp4_lexer.cc header for available definitions */
+ buffer = parser4__create_buffer(f, 65536 /*buffer size*/);
+ if (!buffer) {
+ fatal("cannot scan file " + filename);
+ }
+ parser4__switch_to_buffer(buffer);
+}
+
+void
+Parser4Context::scanEnd() {
+ if (sfile_)
+ fclose(sfile_);
+ sfile_ = 0;
+ static_cast<void>(parser4_lex_destroy());
+ /* Close files */
+ while (!sfiles_.empty()) {
+ FILE* f = sfiles_.back();
+ if (f) {
+ fclose(f);
+ }
+ sfiles_.pop_back();
+ }
+ /* Delete states */
+ while (!states_.empty()) {
+ parser4__delete_buffer(states_.back());
+ states_.pop_back();
+ }
+}
+
+void
+Parser4Context::includeFile(const std::string& filename) {
+ if (states_.size() > 10) {
+ fatal("Too many nested include.");
+ }
+
+ FILE* f = fopen(filename.c_str(), "r");
+ if (!f) {
+ fatal("Can't open include file " + filename);
+ }
+ if (sfile_) {
+ sfiles_.push_back(sfile_);
+ }
+ sfile_ = f;
+ states_.push_back(YY_CURRENT_BUFFER);
+ YY_BUFFER_STATE buffer;
+ buffer = parser4__create_buffer(f, 65536 /*buffer size*/);
+ if (!buffer) {
+ fatal( "Can't scan include file " + filename);
+ }
+ parser4__switch_to_buffer(buffer);
+ files_.push_back(file_);
+ file_ = filename;
+ locs_.push_back(loc_);
+ loc_.initialize(&file_);
+
+ BEGIN(INITIAL);
+}
+
+namespace {
+/** To avoid unused function error */
+class Dummy {
+ /* cppcheck-suppress unusedPrivateFunction */
+ void dummy() { yy_fatal_error("Fix me: how to disable its definition?"); }
+};
+}
+#endif /* !__clang_analyzer__ */
diff --git a/src/bin/dhcp4/dhcp4_log.cc b/src/bin/dhcp4/dhcp4_log.cc
new file mode 100644
index 0000000..315ca35
--- /dev/null
+++ b/src/bin/dhcp4/dhcp4_log.cc
@@ -0,0 +1,43 @@
+// Copyright (C) 2012-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/.
+
+/// @file dhcp4_log.cc
+/// Contains the loggers used by the DHCPv4 server component.
+
+#include <config.h>
+
+#include <dhcp4/dhcp4_log.h>
+
+namespace isc {
+namespace dhcp {
+
+extern const int DBG_DHCP4_START = isc::log::DBGLVL_START_SHUT;
+extern const int DBG_DHCP4_SHUT = isc::log::DBGLVL_START_SHUT;
+extern const int DBG_DHCP4_COMMAND = isc::log::DBGLVL_COMMAND;
+extern const int DBG_DHCP4_BASIC = isc::log::DBGLVL_TRACE_BASIC;
+extern const int DBG_DHCP4_HOOKS = isc::log::DBGLVL_TRACE_BASIC;
+extern const int DBG_DHCP4_BASIC_DATA = isc::log::DBGLVL_TRACE_BASIC_DATA;
+extern const int DBG_DHCP4_DETAIL = isc::log::DBGLVL_TRACE_DETAIL;
+extern const int DBG_DHCP4_DETAIL_DATA = isc::log::DBGLVL_TRACE_DETAIL_DATA;
+
+const char* DHCP4_ROOT_LOGGER_NAME = "kea-dhcp4";
+const char* DHCP4_APP_LOGGER_NAME = "dhcp4";
+const char* DHCP4_BAD_PACKET_LOGGER_NAME = "bad-packets";
+const char* DHCP4_PACKET_LOGGER_NAME = "packets";
+const char* DHCP4_OPTIONS_LOGGER_NAME = "options";
+const char* DHCP4_DDNS_LOGGER_NAME = "ddns";
+const char* DHCP4_LEASE_LOGGER_NAME = "leases";
+
+isc::log::Logger dhcp4_logger(DHCP4_APP_LOGGER_NAME);
+isc::log::Logger bad_packet4_logger(DHCP4_BAD_PACKET_LOGGER_NAME);
+isc::log::Logger packet4_logger(DHCP4_PACKET_LOGGER_NAME);
+isc::log::Logger options4_logger(DHCP4_OPTIONS_LOGGER_NAME);
+isc::log::Logger ddns4_logger(DHCP4_DDNS_LOGGER_NAME);
+isc::log::Logger lease4_logger(DHCP4_LEASE_LOGGER_NAME);
+
+} // namespace dhcp
+} // namespace isc
+
diff --git a/src/bin/dhcp4/dhcp4_log.h b/src/bin/dhcp4/dhcp4_log.h
new file mode 100644
index 0000000..3b03c2a
--- /dev/null
+++ b/src/bin/dhcp4/dhcp4_log.h
@@ -0,0 +1,127 @@
+// Copyright (C) 2012-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/.
+
+/// @file dhcp4_log.h
+/// Contains declarations for loggers used by the DHCPv4 server component.
+
+#ifndef DHCP4_LOG_H
+#define DHCP4_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <dhcp4/dhcp4_messages.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @name Constants defining debug levels for logging in DHCPv4 server.
+//@{
+
+/// @brief Debug level used to log information during server startup.
+extern const int DBG_DHCP4_START;
+
+/// @brief Debug level used to log information during server shutdown.
+extern const int DBG_DHCP4_SHUT;
+
+/// @brief Debug level used to log receiving commands.
+extern const int DBG_DHCP4_COMMAND;
+
+/// @brief Debug level used to trace basic operations within the code.
+extern const int DBG_DHCP4_BASIC;
+
+/// @brief Debug level used to trace hook related operations
+extern const int DBG_DHCP4_HOOKS;
+
+/// @brief Debug level used to log the traces with some basic data.
+///
+/// The basic data includes summary information, e.g. summary of the
+/// information returned by a particular function. It may also include
+/// more detailed information in cases when it is warranted and the
+/// extraction of the data doesn't impact the server's performance
+/// significantly.
+extern const int DBG_DHCP4_BASIC_DATA;
+
+/// @brief Debug level used to trace detailed errors.
+///
+/// Trace detailed operations, including errors raised when processing invalid
+/// packets. (These are not logged at severities of WARN or higher for fear
+/// that a set of deliberately invalid packets set to the server could overwhelm
+/// the logging.)
+extern const int DBG_DHCP4_DETAIL;
+
+/// @brief This level is used to log the contents of packets received and sent.
+extern const int DBG_DHCP4_DETAIL_DATA;
+
+//@}
+
+/// @name Constants holding names of loggers for the DHCPv4 server.
+//@{
+
+/// @brief Defines the name of the root level (default) logger.
+extern const char* DHCP4_ROOT_LOGGER_NAME;
+
+/// @brief Name of the base logger for DHCPv4 server.
+extern const char* DHCP4_APP_LOGGER_NAME;
+
+/// @brief Name of the logger for rejected packets.
+extern const char* DHCP4_BAD_PACKET_LOGGER_NAME;
+
+/// @brief Name of the logger for processed packets.
+extern const char* DHCP4_PACKET_LOGGER_NAME;
+
+/// @brief Name of the logger for options parser.
+extern const char* DHCP4_OPTIONS_LOGGER_NAME;
+
+/// @brief Name of the logger for hostname or FQDN processing.
+extern const char* DHCP4_DDNS_LOGGER_NAME;
+
+/// @brief Name of the logger for lease allocation logic.
+extern const char* DHCP4_LEASE_LOGGER_NAME;
+
+//@}
+
+/// @name Loggers used by the DHCPv4 server
+//@{
+
+/// @brief Base logger for DHCPv4 server.
+extern isc::log::Logger dhcp4_logger;
+
+/// @brief Logger for rejected packets.
+///
+/// Here "bad packets" are packets that are either dropped (i.e malformed,
+/// unsupported types) or packets that are rejected and NAKed for logical
+/// reasons.
+extern isc::log::Logger bad_packet4_logger;
+
+/// @brief Logger for processed packets.
+///
+/// This logger is used to issue log messages related to the reception and
+/// sending DHCP packets.
+extern isc::log::Logger packet4_logger;
+
+/// @brief Logger for options parser.
+///
+/// This logger is used to issue log messages related to processing of the
+/// DHCP options
+extern isc::log::Logger options4_logger;
+
+/// @brief Logger for Hostname or FQDN processing.
+///
+/// This logger is used to issue log messages related to processing the
+/// hostnames, FQDNs and sending name change requests to D2.
+extern isc::log::Logger ddns4_logger;
+
+/// @brief Logger for lease allocation logic.
+///
+/// This logger is used to issue log messages related to lease allocation.
+extern isc::log::Logger lease4_logger;
+
+//@}
+
+} // namespace dhcp4
+} // namespace isc
+
+#endif // DHCP4_LOG_H
diff --git a/src/bin/dhcp4/dhcp4_messages.cc b/src/bin/dhcp4/dhcp4_messages.cc
new file mode 100644
index 0000000..c789ab4
--- /dev/null
+++ b/src/bin/dhcp4/dhcp4_messages.cc
@@ -0,0 +1,333 @@
+// File created from ../../../src/bin/dhcp4/dhcp4_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace dhcp {
+
+extern const isc::log::MessageID DHCP4_ACTIVATE_INTERFACE = "DHCP4_ACTIVATE_INTERFACE";
+extern const isc::log::MessageID DHCP4_ALREADY_RUNNING = "DHCP4_ALREADY_RUNNING";
+extern const isc::log::MessageID DHCP4_BUFFER_RECEIVED = "DHCP4_BUFFER_RECEIVED";
+extern const isc::log::MessageID DHCP4_BUFFER_RECEIVE_FAIL = "DHCP4_BUFFER_RECEIVE_FAIL";
+extern const isc::log::MessageID DHCP4_BUFFER_UNPACK = "DHCP4_BUFFER_UNPACK";
+extern const isc::log::MessageID DHCP4_BUFFER_WAIT_SIGNAL = "DHCP4_BUFFER_WAIT_SIGNAL";
+extern const isc::log::MessageID DHCP4_CB_ON_DEMAND_FETCH_UPDATES_FAIL = "DHCP4_CB_ON_DEMAND_FETCH_UPDATES_FAIL";
+extern const isc::log::MessageID DHCP4_CB_PERIODIC_FETCH_UPDATES_FAIL = "DHCP4_CB_PERIODIC_FETCH_UPDATES_FAIL";
+extern const isc::log::MessageID DHCP4_CB_PERIODIC_FETCH_UPDATES_RETRIES_EXHAUSTED = "DHCP4_CB_PERIODIC_FETCH_UPDATES_RETRIES_EXHAUSTED";
+extern const isc::log::MessageID DHCP4_CLASS_ASSIGNED = "DHCP4_CLASS_ASSIGNED";
+extern const isc::log::MessageID DHCP4_CLASS_UNCONFIGURED = "DHCP4_CLASS_UNCONFIGURED";
+extern const isc::log::MessageID DHCP4_CLASS_UNDEFINED = "DHCP4_CLASS_UNDEFINED";
+extern const isc::log::MessageID DHCP4_CLASS_UNTESTABLE = "DHCP4_CLASS_UNTESTABLE";
+extern const isc::log::MessageID DHCP4_CLIENTID_IGNORED_FOR_LEASES = "DHCP4_CLIENTID_IGNORED_FOR_LEASES";
+extern const isc::log::MessageID DHCP4_CLIENT_FQDN_DATA = "DHCP4_CLIENT_FQDN_DATA";
+extern const isc::log::MessageID DHCP4_CLIENT_FQDN_PROCESS = "DHCP4_CLIENT_FQDN_PROCESS";
+extern const isc::log::MessageID DHCP4_CLIENT_HOSTNAME_DATA = "DHCP4_CLIENT_HOSTNAME_DATA";
+extern const isc::log::MessageID DHCP4_CLIENT_HOSTNAME_MALFORMED = "DHCP4_CLIENT_HOSTNAME_MALFORMED";
+extern const isc::log::MessageID DHCP4_CLIENT_HOSTNAME_PROCESS = "DHCP4_CLIENT_HOSTNAME_PROCESS";
+extern const isc::log::MessageID DHCP4_CLIENT_NAME_PROC_FAIL = "DHCP4_CLIENT_NAME_PROC_FAIL";
+extern const isc::log::MessageID DHCP4_COMMAND_RECEIVED = "DHCP4_COMMAND_RECEIVED";
+extern const isc::log::MessageID DHCP4_CONFIG_COMPLETE = "DHCP4_CONFIG_COMPLETE";
+extern const isc::log::MessageID DHCP4_CONFIG_FETCH = "DHCP4_CONFIG_FETCH";
+extern const isc::log::MessageID DHCP4_CONFIG_LOAD_FAIL = "DHCP4_CONFIG_LOAD_FAIL";
+extern const isc::log::MessageID DHCP4_CONFIG_NEW_SUBNET = "DHCP4_CONFIG_NEW_SUBNET";
+extern const isc::log::MessageID DHCP4_CONFIG_OPTION_DUPLICATE = "DHCP4_CONFIG_OPTION_DUPLICATE";
+extern const isc::log::MessageID DHCP4_CONFIG_PACKET_QUEUE = "DHCP4_CONFIG_PACKET_QUEUE";
+extern const isc::log::MessageID DHCP4_CONFIG_RECEIVED = "DHCP4_CONFIG_RECEIVED";
+extern const isc::log::MessageID DHCP4_CONFIG_START = "DHCP4_CONFIG_START";
+extern const isc::log::MessageID DHCP4_CONFIG_SYNTAX_WARNING = "DHCP4_CONFIG_SYNTAX_WARNING";
+extern const isc::log::MessageID DHCP4_CONFIG_UNRECOVERABLE_ERROR = "DHCP4_CONFIG_UNRECOVERABLE_ERROR";
+extern const isc::log::MessageID DHCP4_CONFIG_UNSUPPORTED_OBJECT = "DHCP4_CONFIG_UNSUPPORTED_OBJECT";
+extern const isc::log::MessageID DHCP4_CONFIG_UPDATE = "DHCP4_CONFIG_UPDATE";
+extern const isc::log::MessageID DHCP4_DB_RECONNECT_DISABLED = "DHCP4_DB_RECONNECT_DISABLED";
+extern const isc::log::MessageID DHCP4_DB_RECONNECT_FAILED = "DHCP4_DB_RECONNECT_FAILED";
+extern const isc::log::MessageID DHCP4_DB_RECONNECT_LOST_CONNECTION = "DHCP4_DB_RECONNECT_LOST_CONNECTION";
+extern const isc::log::MessageID DHCP4_DB_RECONNECT_NO_DB_CTL = "DHCP4_DB_RECONNECT_NO_DB_CTL";
+extern const isc::log::MessageID DHCP4_DB_RECONNECT_SUCCEEDED = "DHCP4_DB_RECONNECT_SUCCEEDED";
+extern const isc::log::MessageID DHCP4_DDNS_REQUEST_SEND_FAILED = "DHCP4_DDNS_REQUEST_SEND_FAILED";
+extern const isc::log::MessageID DHCP4_DEACTIVATE_INTERFACE = "DHCP4_DEACTIVATE_INTERFACE";
+extern const isc::log::MessageID DHCP4_DECLINE_FAIL = "DHCP4_DECLINE_FAIL";
+extern const isc::log::MessageID DHCP4_DECLINE_LEASE = "DHCP4_DECLINE_LEASE";
+extern const isc::log::MessageID DHCP4_DECLINE_LEASE_MISMATCH = "DHCP4_DECLINE_LEASE_MISMATCH";
+extern const isc::log::MessageID DHCP4_DECLINE_LEASE_NOT_FOUND = "DHCP4_DECLINE_LEASE_NOT_FOUND";
+extern const isc::log::MessageID DHCP4_DEFERRED_OPTION_MISSING = "DHCP4_DEFERRED_OPTION_MISSING";
+extern const isc::log::MessageID DHCP4_DEFERRED_OPTION_UNPACK_FAIL = "DHCP4_DEFERRED_OPTION_UNPACK_FAIL";
+extern const isc::log::MessageID DHCP4_DEVELOPMENT_VERSION = "DHCP4_DEVELOPMENT_VERSION";
+extern const isc::log::MessageID DHCP4_DHCP4O6_BAD_PACKET = "DHCP4_DHCP4O6_BAD_PACKET";
+extern const isc::log::MessageID DHCP4_DHCP4O6_PACKET_RECEIVED = "DHCP4_DHCP4O6_PACKET_RECEIVED";
+extern const isc::log::MessageID DHCP4_DHCP4O6_PACKET_SEND = "DHCP4_DHCP4O6_PACKET_SEND";
+extern const isc::log::MessageID DHCP4_DHCP4O6_PACKET_SEND_FAIL = "DHCP4_DHCP4O6_PACKET_SEND_FAIL";
+extern const isc::log::MessageID DHCP4_DHCP4O6_RECEIVE_FAIL = "DHCP4_DHCP4O6_RECEIVE_FAIL";
+extern const isc::log::MessageID DHCP4_DHCP4O6_RECEIVING = "DHCP4_DHCP4O6_RECEIVING";
+extern const isc::log::MessageID DHCP4_DHCP4O6_RESPONSE_DATA = "DHCP4_DHCP4O6_RESPONSE_DATA";
+extern const isc::log::MessageID DHCP4_DYNAMIC_RECONFIGURATION = "DHCP4_DYNAMIC_RECONFIGURATION";
+extern const isc::log::MessageID DHCP4_DYNAMIC_RECONFIGURATION_FAIL = "DHCP4_DYNAMIC_RECONFIGURATION_FAIL";
+extern const isc::log::MessageID DHCP4_DYNAMIC_RECONFIGURATION_SUCCESS = "DHCP4_DYNAMIC_RECONFIGURATION_SUCCESS";
+extern const isc::log::MessageID DHCP4_EMPTY_HOSTNAME = "DHCP4_EMPTY_HOSTNAME";
+extern const isc::log::MessageID DHCP4_FLEX_ID = "DHCP4_FLEX_ID";
+extern const isc::log::MessageID DHCP4_GENERATE_FQDN = "DHCP4_GENERATE_FQDN";
+extern const isc::log::MessageID DHCP4_HANDLE_SIGNAL_EXCEPTION = "DHCP4_HANDLE_SIGNAL_EXCEPTION";
+extern const isc::log::MessageID DHCP4_HOOKS_LIBS_RELOAD_FAIL = "DHCP4_HOOKS_LIBS_RELOAD_FAIL";
+extern const isc::log::MessageID DHCP4_HOOK_BUFFER_RCVD_DROP = "DHCP4_HOOK_BUFFER_RCVD_DROP";
+extern const isc::log::MessageID DHCP4_HOOK_BUFFER_RCVD_SKIP = "DHCP4_HOOK_BUFFER_RCVD_SKIP";
+extern const isc::log::MessageID DHCP4_HOOK_BUFFER_SEND_SKIP = "DHCP4_HOOK_BUFFER_SEND_SKIP";
+extern const isc::log::MessageID DHCP4_HOOK_DDNS_UPDATE = "DHCP4_HOOK_DDNS_UPDATE";
+extern const isc::log::MessageID DHCP4_HOOK_DECLINE_SKIP = "DHCP4_HOOK_DECLINE_SKIP";
+extern const isc::log::MessageID DHCP4_HOOK_LEASE4_RELEASE_SKIP = "DHCP4_HOOK_LEASE4_RELEASE_SKIP";
+extern const isc::log::MessageID DHCP4_HOOK_LEASES4_COMMITTED_DROP = "DHCP4_HOOK_LEASES4_COMMITTED_DROP";
+extern const isc::log::MessageID DHCP4_HOOK_LEASES4_COMMITTED_PARK = "DHCP4_HOOK_LEASES4_COMMITTED_PARK";
+extern const isc::log::MessageID DHCP4_HOOK_LEASES4_PARKING_LOT_FULL = "DHCP4_HOOK_LEASES4_PARKING_LOT_FULL";
+extern const isc::log::MessageID DHCP4_HOOK_PACKET_RCVD_SKIP = "DHCP4_HOOK_PACKET_RCVD_SKIP";
+extern const isc::log::MessageID DHCP4_HOOK_PACKET_SEND_DROP = "DHCP4_HOOK_PACKET_SEND_DROP";
+extern const isc::log::MessageID DHCP4_HOOK_PACKET_SEND_SKIP = "DHCP4_HOOK_PACKET_SEND_SKIP";
+extern const isc::log::MessageID DHCP4_HOOK_SUBNET4_SELECT_DROP = "DHCP4_HOOK_SUBNET4_SELECT_DROP";
+extern const isc::log::MessageID DHCP4_HOOK_SUBNET4_SELECT_SKIP = "DHCP4_HOOK_SUBNET4_SELECT_SKIP";
+extern const isc::log::MessageID DHCP4_INFORM_DIRECT_REPLY = "DHCP4_INFORM_DIRECT_REPLY";
+extern const isc::log::MessageID DHCP4_INIT_FAIL = "DHCP4_INIT_FAIL";
+extern const isc::log::MessageID DHCP4_INIT_REBOOT = "DHCP4_INIT_REBOOT";
+extern const isc::log::MessageID DHCP4_LEASE_ADVERT = "DHCP4_LEASE_ADVERT";
+extern const isc::log::MessageID DHCP4_LEASE_ALLOC = "DHCP4_LEASE_ALLOC";
+extern const isc::log::MessageID DHCP4_LEASE_REUSE = "DHCP4_LEASE_REUSE";
+extern const isc::log::MessageID DHCP4_MULTI_THREADING_INFO = "DHCP4_MULTI_THREADING_INFO";
+extern const isc::log::MessageID DHCP4_NCR_CREATION_FAILED = "DHCP4_NCR_CREATION_FAILED";
+extern const isc::log::MessageID DHCP4_NOT_RUNNING = "DHCP4_NOT_RUNNING";
+extern const isc::log::MessageID DHCP4_NO_LEASE_INIT_REBOOT = "DHCP4_NO_LEASE_INIT_REBOOT";
+extern const isc::log::MessageID DHCP4_NO_SOCKETS_OPEN = "DHCP4_NO_SOCKETS_OPEN";
+extern const isc::log::MessageID DHCP4_OPEN_CONFIG_DB = "DHCP4_OPEN_CONFIG_DB";
+extern const isc::log::MessageID DHCP4_OPEN_SOCKET = "DHCP4_OPEN_SOCKET";
+extern const isc::log::MessageID DHCP4_OPEN_SOCKETS_FAILED = "DHCP4_OPEN_SOCKETS_FAILED";
+extern const isc::log::MessageID DHCP4_OPEN_SOCKETS_NO_RECONNECT_CTL = "DHCP4_OPEN_SOCKETS_NO_RECONNECT_CTL";
+extern const isc::log::MessageID DHCP4_OPEN_SOCKET_FAIL = "DHCP4_OPEN_SOCKET_FAIL";
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0001 = "DHCP4_PACKET_DROP_0001";
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0002 = "DHCP4_PACKET_DROP_0002";
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0003 = "DHCP4_PACKET_DROP_0003";
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0004 = "DHCP4_PACKET_DROP_0004";
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0005 = "DHCP4_PACKET_DROP_0005";
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0006 = "DHCP4_PACKET_DROP_0006";
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0007 = "DHCP4_PACKET_DROP_0007";
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0008 = "DHCP4_PACKET_DROP_0008";
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0009 = "DHCP4_PACKET_DROP_0009";
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0010 = "DHCP4_PACKET_DROP_0010";
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0011 = "DHCP4_PACKET_DROP_0011";
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0012 = "DHCP4_PACKET_DROP_0012";
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0013 = "DHCP4_PACKET_DROP_0013";
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0014 = "DHCP4_PACKET_DROP_0014";
+extern const isc::log::MessageID DHCP4_PACKET_NAK_0001 = "DHCP4_PACKET_NAK_0001";
+extern const isc::log::MessageID DHCP4_PACKET_NAK_0002 = "DHCP4_PACKET_NAK_0002";
+extern const isc::log::MessageID DHCP4_PACKET_NAK_0003 = "DHCP4_PACKET_NAK_0003";
+extern const isc::log::MessageID DHCP4_PACKET_NAK_0004 = "DHCP4_PACKET_NAK_0004";
+extern const isc::log::MessageID DHCP4_PACKET_OPTIONS_SKIPPED = "DHCP4_PACKET_OPTIONS_SKIPPED";
+extern const isc::log::MessageID DHCP4_PACKET_OPTION_UNPACK_FAIL = "DHCP4_PACKET_OPTION_UNPACK_FAIL";
+extern const isc::log::MessageID DHCP4_PACKET_PACK = "DHCP4_PACKET_PACK";
+extern const isc::log::MessageID DHCP4_PACKET_PACK_FAIL = "DHCP4_PACKET_PACK_FAIL";
+extern const isc::log::MessageID DHCP4_PACKET_PROCESS_EXCEPTION = "DHCP4_PACKET_PROCESS_EXCEPTION";
+extern const isc::log::MessageID DHCP4_PACKET_PROCESS_STD_EXCEPTION = "DHCP4_PACKET_PROCESS_STD_EXCEPTION";
+extern const isc::log::MessageID DHCP4_PACKET_QUEUE_FULL = "DHCP4_PACKET_QUEUE_FULL";
+extern const isc::log::MessageID DHCP4_PACKET_RECEIVED = "DHCP4_PACKET_RECEIVED";
+extern const isc::log::MessageID DHCP4_PACKET_SEND = "DHCP4_PACKET_SEND";
+extern const isc::log::MessageID DHCP4_PACKET_SEND_FAIL = "DHCP4_PACKET_SEND_FAIL";
+extern const isc::log::MessageID DHCP4_PARSER_COMMIT_EXCEPTION = "DHCP4_PARSER_COMMIT_EXCEPTION";
+extern const isc::log::MessageID DHCP4_PARSER_COMMIT_FAIL = "DHCP4_PARSER_COMMIT_FAIL";
+extern const isc::log::MessageID DHCP4_PARSER_EXCEPTION = "DHCP4_PARSER_EXCEPTION";
+extern const isc::log::MessageID DHCP4_PARSER_FAIL = "DHCP4_PARSER_FAIL";
+extern const isc::log::MessageID DHCP4_POST_ALLOCATION_NAME_UPDATE_FAIL = "DHCP4_POST_ALLOCATION_NAME_UPDATE_FAIL";
+extern const isc::log::MessageID DHCP4_QUERY_DATA = "DHCP4_QUERY_DATA";
+extern const isc::log::MessageID DHCP4_RECLAIM_EXPIRED_LEASES_FAIL = "DHCP4_RECLAIM_EXPIRED_LEASES_FAIL";
+extern const isc::log::MessageID DHCP4_RELEASE = "DHCP4_RELEASE";
+extern const isc::log::MessageID DHCP4_RELEASE_EXCEPTION = "DHCP4_RELEASE_EXCEPTION";
+extern const isc::log::MessageID DHCP4_RELEASE_FAIL = "DHCP4_RELEASE_FAIL";
+extern const isc::log::MessageID DHCP4_RELEASE_FAIL_NO_LEASE = "DHCP4_RELEASE_FAIL_NO_LEASE";
+extern const isc::log::MessageID DHCP4_RELEASE_FAIL_WRONG_CLIENT = "DHCP4_RELEASE_FAIL_WRONG_CLIENT";
+extern const isc::log::MessageID DHCP4_RESERVATIONS_LOOKUP_FIRST_ENABLED = "DHCP4_RESERVATIONS_LOOKUP_FIRST_ENABLED";
+extern const isc::log::MessageID DHCP4_RESERVED_HOSTNAME_ASSIGNED = "DHCP4_RESERVED_HOSTNAME_ASSIGNED";
+extern const isc::log::MessageID DHCP4_RESPONSE_DATA = "DHCP4_RESPONSE_DATA";
+extern const isc::log::MessageID DHCP4_RESPONSE_FQDN_DATA = "DHCP4_RESPONSE_FQDN_DATA";
+extern const isc::log::MessageID DHCP4_RESPONSE_HOSTNAME_DATA = "DHCP4_RESPONSE_HOSTNAME_DATA";
+extern const isc::log::MessageID DHCP4_RESPONSE_HOSTNAME_GENERATE = "DHCP4_RESPONSE_HOSTNAME_GENERATE";
+extern const isc::log::MessageID DHCP4_SERVER_FAILED = "DHCP4_SERVER_FAILED";
+extern const isc::log::MessageID DHCP4_SHUTDOWN = "DHCP4_SHUTDOWN";
+extern const isc::log::MessageID DHCP4_SHUTDOWN_REQUEST = "DHCP4_SHUTDOWN_REQUEST";
+extern const isc::log::MessageID DHCP4_SRV_CONSTRUCT_ERROR = "DHCP4_SRV_CONSTRUCT_ERROR";
+extern const isc::log::MessageID DHCP4_SRV_D2STOP_ERROR = "DHCP4_SRV_D2STOP_ERROR";
+extern const isc::log::MessageID DHCP4_SRV_DHCP4O6_ERROR = "DHCP4_SRV_DHCP4O6_ERROR";
+extern const isc::log::MessageID DHCP4_SRV_UNLOAD_LIBRARIES_ERROR = "DHCP4_SRV_UNLOAD_LIBRARIES_ERROR";
+extern const isc::log::MessageID DHCP4_STARTED = "DHCP4_STARTED";
+extern const isc::log::MessageID DHCP4_STARTING = "DHCP4_STARTING";
+extern const isc::log::MessageID DHCP4_START_INFO = "DHCP4_START_INFO";
+extern const isc::log::MessageID DHCP4_SUBNET_DATA = "DHCP4_SUBNET_DATA";
+extern const isc::log::MessageID DHCP4_SUBNET_DYNAMICALLY_CHANGED = "DHCP4_SUBNET_DYNAMICALLY_CHANGED";
+extern const isc::log::MessageID DHCP4_SUBNET_SELECTED = "DHCP4_SUBNET_SELECTED";
+extern const isc::log::MessageID DHCP4_SUBNET_SELECTION_FAILED = "DHCP4_SUBNET_SELECTION_FAILED";
+extern const isc::log::MessageID DHCP4_TESTING_MODE_SEND_TO_SOURCE_ENABLED = "DHCP4_TESTING_MODE_SEND_TO_SOURCE_ENABLED";
+extern const isc::log::MessageID DHCP4_UNKNOWN_ADDRESS_REQUESTED = "DHCP4_UNKNOWN_ADDRESS_REQUESTED";
+extern const isc::log::MessageID DHCP6_DHCP4O6_PACKET_RECEIVED = "DHCP6_DHCP4O6_PACKET_RECEIVED";
+
+} // namespace dhcp
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "DHCP4_ACTIVATE_INTERFACE", "activating interface %1",
+ "DHCP4_ALREADY_RUNNING", "%1 already running? %2",
+ "DHCP4_BUFFER_RECEIVED", "received buffer from %1:%2 to %3:%4 over interface %5",
+ "DHCP4_BUFFER_RECEIVE_FAIL", "error on attempt to receive packet: %1",
+ "DHCP4_BUFFER_UNPACK", "parsing buffer received from %1 to %2 over interface %3",
+ "DHCP4_BUFFER_WAIT_SIGNAL", "signal received while waiting for next packet",
+ "DHCP4_CB_ON_DEMAND_FETCH_UPDATES_FAIL", "error on demand attempt to fetch configuration updates from the configuration backend(s): %1",
+ "DHCP4_CB_PERIODIC_FETCH_UPDATES_FAIL", "error on periodic attempt to fetch configuration updates from the configuration backend(s): %1",
+ "DHCP4_CB_PERIODIC_FETCH_UPDATES_RETRIES_EXHAUSTED", "maximum number of configuration fetch attempts: 10, has been exhausted without success",
+ "DHCP4_CLASS_ASSIGNED", "%1: client packet has been assigned to the following class(es): %2",
+ "DHCP4_CLASS_UNCONFIGURED", "%1: client packet belongs to an unconfigured class: %2",
+ "DHCP4_CLASS_UNDEFINED", "required class %1 has no definition",
+ "DHCP4_CLASS_UNTESTABLE", "required class %1 has no test expression",
+ "DHCP4_CLIENTID_IGNORED_FOR_LEASES", "%1: not using client identifier for lease allocation for subnet %2",
+ "DHCP4_CLIENT_FQDN_DATA", "%1: Client sent FQDN option: %2",
+ "DHCP4_CLIENT_FQDN_PROCESS", "%1: processing Client FQDN option",
+ "DHCP4_CLIENT_HOSTNAME_DATA", "%1: client sent Hostname option: %2",
+ "DHCP4_CLIENT_HOSTNAME_MALFORMED", "%1: client hostname option malformed: %2",
+ "DHCP4_CLIENT_HOSTNAME_PROCESS", "%1: processing client's Hostname option",
+ "DHCP4_CLIENT_NAME_PROC_FAIL", "%1: failed to process the fqdn or hostname sent by a client: %2",
+ "DHCP4_COMMAND_RECEIVED", "received command %1, arguments: %2",
+ "DHCP4_CONFIG_COMPLETE", "DHCPv4 server has completed configuration: %1",
+ "DHCP4_CONFIG_FETCH", "Fetching configuration data from config backends.",
+ "DHCP4_CONFIG_LOAD_FAIL", "configuration error using file: %1, reason: %2",
+ "DHCP4_CONFIG_NEW_SUBNET", "a new subnet has been added to configuration: %1",
+ "DHCP4_CONFIG_OPTION_DUPLICATE", "multiple options with the code %1 added to the subnet %2",
+ "DHCP4_CONFIG_PACKET_QUEUE", "DHCPv4 packet queue info after configuration: %1",
+ "DHCP4_CONFIG_RECEIVED", "received configuration %1",
+ "DHCP4_CONFIG_START", "DHCPv4 server is processing the following configuration: %1",
+ "DHCP4_CONFIG_SYNTAX_WARNING", "configuration syntax warning: %1",
+ "DHCP4_CONFIG_UNRECOVERABLE_ERROR", "DHCPv4 server new configuration failed with an error which cannot be recovered",
+ "DHCP4_CONFIG_UNSUPPORTED_OBJECT", "DHCPv4 server configuration includes an unsupported object: %1",
+ "DHCP4_CONFIG_UPDATE", "updated configuration received: %1",
+ "DHCP4_DB_RECONNECT_DISABLED", "database reconnect is disabled: max-reconnect-tries %1, reconnect-wait-time %2",
+ "DHCP4_DB_RECONNECT_FAILED", "maximum number of database reconnect attempts: %1, has been exhausted without success",
+ "DHCP4_DB_RECONNECT_LOST_CONNECTION", "database connection lost.",
+ "DHCP4_DB_RECONNECT_NO_DB_CTL", "unexpected error in database reconnect",
+ "DHCP4_DB_RECONNECT_SUCCEEDED", "database connection recovered.",
+ "DHCP4_DDNS_REQUEST_SEND_FAILED", "failed sending a request to kea-dhcp-ddns, error: %1, ncr: %2",
+ "DHCP4_DEACTIVATE_INTERFACE", "deactivate interface %1",
+ "DHCP4_DECLINE_FAIL", "%1: error on decline lease for address %2: %3",
+ "DHCP4_DECLINE_LEASE", "Received DHCPDECLINE for addr %1 from client %2. The lease will be unavailable for %3 seconds.",
+ "DHCP4_DECLINE_LEASE_MISMATCH", "Received DHCPDECLINE for addr %1 from client %2, but the data doesn't match: received hwaddr: %3, lease hwaddr: %4, received client-id: %5, lease client-id: %6",
+ "DHCP4_DECLINE_LEASE_NOT_FOUND", "Received DHCPDECLINE for addr %1 from client %2, but no such lease found.",
+ "DHCP4_DEFERRED_OPTION_MISSING", "can find deferred option code %1 in the query",
+ "DHCP4_DEFERRED_OPTION_UNPACK_FAIL", "An error unpacking the deferred option %1: %2",
+ "DHCP4_DEVELOPMENT_VERSION", "This software is a development branch of Kea. It is not recommended for production use.",
+ "DHCP4_DHCP4O6_BAD_PACKET", "received malformed DHCPv4o6 packet: %1",
+ "DHCP4_DHCP4O6_PACKET_RECEIVED", "received DHCPv4o6 packet from DHCPv4 server (type %1) for %2 on interface %3",
+ "DHCP4_DHCP4O6_PACKET_SEND", "%1: trying to send packet %2 (type %3) to %4 port %5 on interface %6 encapsulating %7: %8 (type %9)",
+ "DHCP4_DHCP4O6_PACKET_SEND_FAIL", "%1: failed to send DHCPv4o6 packet: %2",
+ "DHCP4_DHCP4O6_RECEIVE_FAIL", "failed to receive DHCPv4o6: %1",
+ "DHCP4_DHCP4O6_RECEIVING", "receiving DHCPv4o6 packet from DHCPv6 server",
+ "DHCP4_DHCP4O6_RESPONSE_DATA", "%1: responding with packet %2 (type %3), packet details: %4",
+ "DHCP4_DYNAMIC_RECONFIGURATION", "initiate server reconfiguration using file: %1, after receiving SIGHUP signal or config-reload command",
+ "DHCP4_DYNAMIC_RECONFIGURATION_FAIL", "dynamic server reconfiguration failed with file: %1",
+ "DHCP4_DYNAMIC_RECONFIGURATION_SUCCESS", "dynamic server reconfiguration succeeded with file: %1",
+ "DHCP4_EMPTY_HOSTNAME", "%1: received empty hostname from the client, skipping processing of this option",
+ "DHCP4_FLEX_ID", "flexible identifier generated for incoming packet: %1",
+ "DHCP4_GENERATE_FQDN", "%1: client did not send a FQDN or hostname; FQDN will be generated for the client",
+ "DHCP4_HANDLE_SIGNAL_EXCEPTION", "An exception was thrown while handing signal: %1",
+ "DHCP4_HOOKS_LIBS_RELOAD_FAIL", "reload of hooks libraries failed",
+ "DHCP4_HOOK_BUFFER_RCVD_DROP", "received buffer from %1 to %2 over interface %3 was dropped because a callout set the drop flag",
+ "DHCP4_HOOK_BUFFER_RCVD_SKIP", "received buffer from %1 to %2 over interface %3 is not parsed because a callout set the next step to SKIP.",
+ "DHCP4_HOOK_BUFFER_SEND_SKIP", "%1: prepared response is dropped because a callout set the next step to SKIP.",
+ "DHCP4_HOOK_DDNS_UPDATE", "A hook has updated the DDNS parameters: hostname %1=>%2, forward update %3=>%4, reverse update %5=>%6",
+ "DHCP4_HOOK_DECLINE_SKIP", "Decline4 hook callouts set status to DROP, ignoring packet.",
+ "DHCP4_HOOK_LEASE4_RELEASE_SKIP", "%1: lease was not released because a callout set the next step to SKIP",
+ "DHCP4_HOOK_LEASES4_COMMITTED_DROP", "%1: packet is dropped, because a callout set the next step to DROP",
+ "DHCP4_HOOK_LEASES4_COMMITTED_PARK", "%1: packet is parked, because a callout set the next step to PARK",
+ "DHCP4_HOOK_LEASES4_PARKING_LOT_FULL", "The parked-packet-limit %1, has been reached, dropping query: %2",
+ "DHCP4_HOOK_PACKET_RCVD_SKIP", "%1: packet is dropped, because a callout set the next step to SKIP",
+ "DHCP4_HOOK_PACKET_SEND_DROP", "%1: prepared DHCPv4 response was not sent because a callout set the next ste to DROP",
+ "DHCP4_HOOK_PACKET_SEND_SKIP", "%1: prepared response is not sent, because a callout set the next stp to SKIP",
+ "DHCP4_HOOK_SUBNET4_SELECT_DROP", "%1: packet was dropped, because a callout set the next step to 'drop'",
+ "DHCP4_HOOK_SUBNET4_SELECT_SKIP", "%1: no subnet was selected, because a callout set the next skip flag",
+ "DHCP4_INFORM_DIRECT_REPLY", "%1: DHCPACK in reply to the DHCPINFORM will be sent directly to %2 over %3",
+ "DHCP4_INIT_FAIL", "failed to initialize Kea server: %1",
+ "DHCP4_INIT_REBOOT", "%1: client is in INIT-REBOOT state and requests address %2",
+ "DHCP4_LEASE_ADVERT", "%1: lease %2 will be advertised",
+ "DHCP4_LEASE_ALLOC", "%1: lease %2 has been allocated for %3 seconds",
+ "DHCP4_LEASE_REUSE", "%1: lease %2 has been reused for %3 seconds",
+ "DHCP4_MULTI_THREADING_INFO", "enabled: %1, number of threads: %2, queue size: %3",
+ "DHCP4_NCR_CREATION_FAILED", "%1: failed to generate name change requests for DNS: %2",
+ "DHCP4_NOT_RUNNING", "DHCPv4 server is not running",
+ "DHCP4_NO_LEASE_INIT_REBOOT", "%1: no lease for address %2 requested by INIT-REBOOT client",
+ "DHCP4_NO_SOCKETS_OPEN", "no interface configured to listen to DHCP traffic",
+ "DHCP4_OPEN_CONFIG_DB", "Opening configuration database: %1",
+ "DHCP4_OPEN_SOCKET", "opening service sockets on port %1",
+ "DHCP4_OPEN_SOCKETS_FAILED", "maximum number of open service sockets attempts: %1, has been exhausted without success",
+ "DHCP4_OPEN_SOCKETS_NO_RECONNECT_CTL", "unexpected error in bind service sockets.",
+ "DHCP4_OPEN_SOCKET_FAIL", "failed to open socket: %1",
+ "DHCP4_PACKET_DROP_0001", "failed to parse packet from %1 to %2, received over interface %3, reason: %4",
+ "DHCP4_PACKET_DROP_0002", "%1, from interface %2: no suitable subnet configured for a direct client",
+ "DHCP4_PACKET_DROP_0003", "%1, from interface %2: it contains a foreign server identifier",
+ "DHCP4_PACKET_DROP_0004", "%1, from interface %2: missing msg-type option",
+ "DHCP4_PACKET_DROP_0005", "%1: unrecognized type %2 in option 53",
+ "DHCP4_PACKET_DROP_0006", "%1: unsupported DHCPv4 message type %2",
+ "DHCP4_PACKET_DROP_0007", "%1: failed to process packet: %2",
+ "DHCP4_PACKET_DROP_0008", "%1: DHCP service is globally disabled",
+ "DHCP4_PACKET_DROP_0009", "%1: Option 53 missing (no DHCP message type), is this a BOOTP packet?",
+ "DHCP4_PACKET_DROP_0010", "dropped as member of the special class 'DROP': %1",
+ "DHCP4_PACKET_DROP_0011", "dropped as sent by the same client than a packet being processed by another thread: dropped %1 by thread %2 as duplicate of %3 processed by %4",
+ "DHCP4_PACKET_DROP_0012", "dropped as sent by the same client than a packet being processed by another thread: dropped %1 by thread %2 as duplicate of %3 processed by %4",
+ "DHCP4_PACKET_DROP_0013", "dropped as member of the special class 'DROP' after host reservation lookup: %1",
+ "DHCP4_PACKET_DROP_0014", "dropped as member of the special class 'DROP' after early global host reservations lookup: %1",
+ "DHCP4_PACKET_NAK_0001", "%1: failed to select a subnet for incoming packet, src %2, type %3",
+ "DHCP4_PACKET_NAK_0002", "%1: invalid address %2 requested by INIT-REBOOT",
+ "DHCP4_PACKET_NAK_0003", "%1: failed to advertise a lease, client sent ciaddr %2, requested-ip-address %3",
+ "DHCP4_PACKET_NAK_0004", "%1: failed to grant a lease, client sent ciaddr %2, requested-ip-address %3",
+ "DHCP4_PACKET_OPTIONS_SKIPPED", "An error unpacking an option, caused subsequent options to be skipped: %1",
+ "DHCP4_PACKET_OPTION_UNPACK_FAIL", "An error unpacking the option %1: %2",
+ "DHCP4_PACKET_PACK", "%1: preparing on-wire format of the packet to be sent",
+ "DHCP4_PACKET_PACK_FAIL", "%1: preparing on-wire-format of the packet to be sent failed %2",
+ "DHCP4_PACKET_PROCESS_EXCEPTION", "exception occurred during packet processing",
+ "DHCP4_PACKET_PROCESS_STD_EXCEPTION", "exception occurred during packet processing: %1",
+ "DHCP4_PACKET_QUEUE_FULL", "multi-threading packet queue is full",
+ "DHCP4_PACKET_RECEIVED", "%1: %2 (type %3) received from %4 to %5 on interface %6",
+ "DHCP4_PACKET_SEND", "%1: trying to send packet %2 (type %3) from %4:%5 to %6:%7 on interface %8",
+ "DHCP4_PACKET_SEND_FAIL", "%1: failed to send DHCPv4 packet: %2",
+ "DHCP4_PARSER_COMMIT_EXCEPTION", "parser failed to commit changes",
+ "DHCP4_PARSER_COMMIT_FAIL", "parser failed to commit changes: %1",
+ "DHCP4_PARSER_EXCEPTION", "failed to create or run parser for configuration element %1",
+ "DHCP4_PARSER_FAIL", "failed to create or run parser for configuration element %1: %2",
+ "DHCP4_POST_ALLOCATION_NAME_UPDATE_FAIL", "%1: failed to update hostname %2 in a lease after address allocation: %3",
+ "DHCP4_QUERY_DATA", "%1, packet details: %2",
+ "DHCP4_RECLAIM_EXPIRED_LEASES_FAIL", "failed to reclaim expired leases: %1",
+ "DHCP4_RELEASE", "%1: address %2 was released properly.",
+ "DHCP4_RELEASE_EXCEPTION", "%1: while trying to release address %2 an exception occurred: %3",
+ "DHCP4_RELEASE_FAIL", "%1: failed to remove lease for address %2",
+ "DHCP4_RELEASE_FAIL_NO_LEASE", "%1: client is trying to release non-existing lease %2",
+ "DHCP4_RELEASE_FAIL_WRONG_CLIENT", "%1: client is trying to release the lease %2 which belongs to a different client",
+ "DHCP4_RESERVATIONS_LOOKUP_FIRST_ENABLED", "Multi-threading is enabled and host reservations lookup is always performed first.",
+ "DHCP4_RESERVED_HOSTNAME_ASSIGNED", "%1: server assigned reserved hostname %2",
+ "DHCP4_RESPONSE_DATA", "%1: responding with packet %2 (type %3), packet details: %4",
+ "DHCP4_RESPONSE_FQDN_DATA", "%1: including FQDN option in the server's response: %2",
+ "DHCP4_RESPONSE_HOSTNAME_DATA", "%1: including Hostname option in the server's response: %2",
+ "DHCP4_RESPONSE_HOSTNAME_GENERATE", "%1: server has generated hostname %2 for the client",
+ "DHCP4_SERVER_FAILED", "server failed: %1",
+ "DHCP4_SHUTDOWN", "server shutdown",
+ "DHCP4_SHUTDOWN_REQUEST", "shutdown of server requested",
+ "DHCP4_SRV_CONSTRUCT_ERROR", "error creating Dhcpv4Srv object, reason: %1",
+ "DHCP4_SRV_D2STOP_ERROR", "error stopping IO with DHCP_DDNS during shutdown: %1",
+ "DHCP4_SRV_DHCP4O6_ERROR", "error stopping IO with DHCPv4o6 during shutdown: %1",
+ "DHCP4_SRV_UNLOAD_LIBRARIES_ERROR", "error unloading hooks libraries during shutdown: %1",
+ "DHCP4_STARTED", "Kea DHCPv4 server version %1 started",
+ "DHCP4_STARTING", "Kea DHCPv4 server version %1 (%2) starting",
+ "DHCP4_START_INFO", "pid: %1, server port: %2, client port: %3, verbose: %4",
+ "DHCP4_SUBNET_DATA", "%1: the selected subnet details: %2",
+ "DHCP4_SUBNET_DYNAMICALLY_CHANGED", "%1: changed selected subnet %2 to subnet %3 from shared network %4 for client assignments",
+ "DHCP4_SUBNET_SELECTED", "%1: the subnet with ID %2 was selected for client assignments",
+ "DHCP4_SUBNET_SELECTION_FAILED", "%1: failed to select subnet for the client",
+ "DHCP4_TESTING_MODE_SEND_TO_SOURCE_ENABLED", "All packets will be send to source address of an incoming packet - use only for testing",
+ "DHCP4_UNKNOWN_ADDRESS_REQUESTED", "%1: client requested an unknown address, client sent ciaddr %2, requested-ip-address %3",
+ "DHCP6_DHCP4O6_PACKET_RECEIVED", "received DHCPv4o6 packet from DHCPv6 server (type %1) for %2 port %3 on interface %4",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/bin/dhcp4/dhcp4_messages.h b/src/bin/dhcp4/dhcp4_messages.h
new file mode 100644
index 0000000..c69c877
--- /dev/null
+++ b/src/bin/dhcp4/dhcp4_messages.h
@@ -0,0 +1,170 @@
+// File created from ../../../src/bin/dhcp4/dhcp4_messages.mes
+
+#ifndef DHCP4_MESSAGES_H
+#define DHCP4_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace dhcp {
+
+extern const isc::log::MessageID DHCP4_ACTIVATE_INTERFACE;
+extern const isc::log::MessageID DHCP4_ALREADY_RUNNING;
+extern const isc::log::MessageID DHCP4_BUFFER_RECEIVED;
+extern const isc::log::MessageID DHCP4_BUFFER_RECEIVE_FAIL;
+extern const isc::log::MessageID DHCP4_BUFFER_UNPACK;
+extern const isc::log::MessageID DHCP4_BUFFER_WAIT_SIGNAL;
+extern const isc::log::MessageID DHCP4_CB_ON_DEMAND_FETCH_UPDATES_FAIL;
+extern const isc::log::MessageID DHCP4_CB_PERIODIC_FETCH_UPDATES_FAIL;
+extern const isc::log::MessageID DHCP4_CB_PERIODIC_FETCH_UPDATES_RETRIES_EXHAUSTED;
+extern const isc::log::MessageID DHCP4_CLASS_ASSIGNED;
+extern const isc::log::MessageID DHCP4_CLASS_UNCONFIGURED;
+extern const isc::log::MessageID DHCP4_CLASS_UNDEFINED;
+extern const isc::log::MessageID DHCP4_CLASS_UNTESTABLE;
+extern const isc::log::MessageID DHCP4_CLIENTID_IGNORED_FOR_LEASES;
+extern const isc::log::MessageID DHCP4_CLIENT_FQDN_DATA;
+extern const isc::log::MessageID DHCP4_CLIENT_FQDN_PROCESS;
+extern const isc::log::MessageID DHCP4_CLIENT_HOSTNAME_DATA;
+extern const isc::log::MessageID DHCP4_CLIENT_HOSTNAME_MALFORMED;
+extern const isc::log::MessageID DHCP4_CLIENT_HOSTNAME_PROCESS;
+extern const isc::log::MessageID DHCP4_CLIENT_NAME_PROC_FAIL;
+extern const isc::log::MessageID DHCP4_COMMAND_RECEIVED;
+extern const isc::log::MessageID DHCP4_CONFIG_COMPLETE;
+extern const isc::log::MessageID DHCP4_CONFIG_FETCH;
+extern const isc::log::MessageID DHCP4_CONFIG_LOAD_FAIL;
+extern const isc::log::MessageID DHCP4_CONFIG_NEW_SUBNET;
+extern const isc::log::MessageID DHCP4_CONFIG_OPTION_DUPLICATE;
+extern const isc::log::MessageID DHCP4_CONFIG_PACKET_QUEUE;
+extern const isc::log::MessageID DHCP4_CONFIG_RECEIVED;
+extern const isc::log::MessageID DHCP4_CONFIG_START;
+extern const isc::log::MessageID DHCP4_CONFIG_SYNTAX_WARNING;
+extern const isc::log::MessageID DHCP4_CONFIG_UNRECOVERABLE_ERROR;
+extern const isc::log::MessageID DHCP4_CONFIG_UNSUPPORTED_OBJECT;
+extern const isc::log::MessageID DHCP4_CONFIG_UPDATE;
+extern const isc::log::MessageID DHCP4_DB_RECONNECT_DISABLED;
+extern const isc::log::MessageID DHCP4_DB_RECONNECT_FAILED;
+extern const isc::log::MessageID DHCP4_DB_RECONNECT_LOST_CONNECTION;
+extern const isc::log::MessageID DHCP4_DB_RECONNECT_NO_DB_CTL;
+extern const isc::log::MessageID DHCP4_DB_RECONNECT_SUCCEEDED;
+extern const isc::log::MessageID DHCP4_DDNS_REQUEST_SEND_FAILED;
+extern const isc::log::MessageID DHCP4_DEACTIVATE_INTERFACE;
+extern const isc::log::MessageID DHCP4_DECLINE_FAIL;
+extern const isc::log::MessageID DHCP4_DECLINE_LEASE;
+extern const isc::log::MessageID DHCP4_DECLINE_LEASE_MISMATCH;
+extern const isc::log::MessageID DHCP4_DECLINE_LEASE_NOT_FOUND;
+extern const isc::log::MessageID DHCP4_DEFERRED_OPTION_MISSING;
+extern const isc::log::MessageID DHCP4_DEFERRED_OPTION_UNPACK_FAIL;
+extern const isc::log::MessageID DHCP4_DEVELOPMENT_VERSION;
+extern const isc::log::MessageID DHCP4_DHCP4O6_BAD_PACKET;
+extern const isc::log::MessageID DHCP4_DHCP4O6_PACKET_RECEIVED;
+extern const isc::log::MessageID DHCP4_DHCP4O6_PACKET_SEND;
+extern const isc::log::MessageID DHCP4_DHCP4O6_PACKET_SEND_FAIL;
+extern const isc::log::MessageID DHCP4_DHCP4O6_RECEIVE_FAIL;
+extern const isc::log::MessageID DHCP4_DHCP4O6_RECEIVING;
+extern const isc::log::MessageID DHCP4_DHCP4O6_RESPONSE_DATA;
+extern const isc::log::MessageID DHCP4_DYNAMIC_RECONFIGURATION;
+extern const isc::log::MessageID DHCP4_DYNAMIC_RECONFIGURATION_FAIL;
+extern const isc::log::MessageID DHCP4_DYNAMIC_RECONFIGURATION_SUCCESS;
+extern const isc::log::MessageID DHCP4_EMPTY_HOSTNAME;
+extern const isc::log::MessageID DHCP4_FLEX_ID;
+extern const isc::log::MessageID DHCP4_GENERATE_FQDN;
+extern const isc::log::MessageID DHCP4_HANDLE_SIGNAL_EXCEPTION;
+extern const isc::log::MessageID DHCP4_HOOKS_LIBS_RELOAD_FAIL;
+extern const isc::log::MessageID DHCP4_HOOK_BUFFER_RCVD_DROP;
+extern const isc::log::MessageID DHCP4_HOOK_BUFFER_RCVD_SKIP;
+extern const isc::log::MessageID DHCP4_HOOK_BUFFER_SEND_SKIP;
+extern const isc::log::MessageID DHCP4_HOOK_DDNS_UPDATE;
+extern const isc::log::MessageID DHCP4_HOOK_DECLINE_SKIP;
+extern const isc::log::MessageID DHCP4_HOOK_LEASE4_RELEASE_SKIP;
+extern const isc::log::MessageID DHCP4_HOOK_LEASES4_COMMITTED_DROP;
+extern const isc::log::MessageID DHCP4_HOOK_LEASES4_COMMITTED_PARK;
+extern const isc::log::MessageID DHCP4_HOOK_LEASES4_PARKING_LOT_FULL;
+extern const isc::log::MessageID DHCP4_HOOK_PACKET_RCVD_SKIP;
+extern const isc::log::MessageID DHCP4_HOOK_PACKET_SEND_DROP;
+extern const isc::log::MessageID DHCP4_HOOK_PACKET_SEND_SKIP;
+extern const isc::log::MessageID DHCP4_HOOK_SUBNET4_SELECT_DROP;
+extern const isc::log::MessageID DHCP4_HOOK_SUBNET4_SELECT_SKIP;
+extern const isc::log::MessageID DHCP4_INFORM_DIRECT_REPLY;
+extern const isc::log::MessageID DHCP4_INIT_FAIL;
+extern const isc::log::MessageID DHCP4_INIT_REBOOT;
+extern const isc::log::MessageID DHCP4_LEASE_ADVERT;
+extern const isc::log::MessageID DHCP4_LEASE_ALLOC;
+extern const isc::log::MessageID DHCP4_LEASE_REUSE;
+extern const isc::log::MessageID DHCP4_MULTI_THREADING_INFO;
+extern const isc::log::MessageID DHCP4_NCR_CREATION_FAILED;
+extern const isc::log::MessageID DHCP4_NOT_RUNNING;
+extern const isc::log::MessageID DHCP4_NO_LEASE_INIT_REBOOT;
+extern const isc::log::MessageID DHCP4_NO_SOCKETS_OPEN;
+extern const isc::log::MessageID DHCP4_OPEN_CONFIG_DB;
+extern const isc::log::MessageID DHCP4_OPEN_SOCKET;
+extern const isc::log::MessageID DHCP4_OPEN_SOCKETS_FAILED;
+extern const isc::log::MessageID DHCP4_OPEN_SOCKETS_NO_RECONNECT_CTL;
+extern const isc::log::MessageID DHCP4_OPEN_SOCKET_FAIL;
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0001;
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0002;
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0003;
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0004;
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0005;
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0006;
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0007;
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0008;
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0009;
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0010;
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0011;
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0012;
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0013;
+extern const isc::log::MessageID DHCP4_PACKET_DROP_0014;
+extern const isc::log::MessageID DHCP4_PACKET_NAK_0001;
+extern const isc::log::MessageID DHCP4_PACKET_NAK_0002;
+extern const isc::log::MessageID DHCP4_PACKET_NAK_0003;
+extern const isc::log::MessageID DHCP4_PACKET_NAK_0004;
+extern const isc::log::MessageID DHCP4_PACKET_OPTIONS_SKIPPED;
+extern const isc::log::MessageID DHCP4_PACKET_OPTION_UNPACK_FAIL;
+extern const isc::log::MessageID DHCP4_PACKET_PACK;
+extern const isc::log::MessageID DHCP4_PACKET_PACK_FAIL;
+extern const isc::log::MessageID DHCP4_PACKET_PROCESS_EXCEPTION;
+extern const isc::log::MessageID DHCP4_PACKET_PROCESS_STD_EXCEPTION;
+extern const isc::log::MessageID DHCP4_PACKET_QUEUE_FULL;
+extern const isc::log::MessageID DHCP4_PACKET_RECEIVED;
+extern const isc::log::MessageID DHCP4_PACKET_SEND;
+extern const isc::log::MessageID DHCP4_PACKET_SEND_FAIL;
+extern const isc::log::MessageID DHCP4_PARSER_COMMIT_EXCEPTION;
+extern const isc::log::MessageID DHCP4_PARSER_COMMIT_FAIL;
+extern const isc::log::MessageID DHCP4_PARSER_EXCEPTION;
+extern const isc::log::MessageID DHCP4_PARSER_FAIL;
+extern const isc::log::MessageID DHCP4_POST_ALLOCATION_NAME_UPDATE_FAIL;
+extern const isc::log::MessageID DHCP4_QUERY_DATA;
+extern const isc::log::MessageID DHCP4_RECLAIM_EXPIRED_LEASES_FAIL;
+extern const isc::log::MessageID DHCP4_RELEASE;
+extern const isc::log::MessageID DHCP4_RELEASE_EXCEPTION;
+extern const isc::log::MessageID DHCP4_RELEASE_FAIL;
+extern const isc::log::MessageID DHCP4_RELEASE_FAIL_NO_LEASE;
+extern const isc::log::MessageID DHCP4_RELEASE_FAIL_WRONG_CLIENT;
+extern const isc::log::MessageID DHCP4_RESERVATIONS_LOOKUP_FIRST_ENABLED;
+extern const isc::log::MessageID DHCP4_RESERVED_HOSTNAME_ASSIGNED;
+extern const isc::log::MessageID DHCP4_RESPONSE_DATA;
+extern const isc::log::MessageID DHCP4_RESPONSE_FQDN_DATA;
+extern const isc::log::MessageID DHCP4_RESPONSE_HOSTNAME_DATA;
+extern const isc::log::MessageID DHCP4_RESPONSE_HOSTNAME_GENERATE;
+extern const isc::log::MessageID DHCP4_SERVER_FAILED;
+extern const isc::log::MessageID DHCP4_SHUTDOWN;
+extern const isc::log::MessageID DHCP4_SHUTDOWN_REQUEST;
+extern const isc::log::MessageID DHCP4_SRV_CONSTRUCT_ERROR;
+extern const isc::log::MessageID DHCP4_SRV_D2STOP_ERROR;
+extern const isc::log::MessageID DHCP4_SRV_DHCP4O6_ERROR;
+extern const isc::log::MessageID DHCP4_SRV_UNLOAD_LIBRARIES_ERROR;
+extern const isc::log::MessageID DHCP4_STARTED;
+extern const isc::log::MessageID DHCP4_STARTING;
+extern const isc::log::MessageID DHCP4_START_INFO;
+extern const isc::log::MessageID DHCP4_SUBNET_DATA;
+extern const isc::log::MessageID DHCP4_SUBNET_DYNAMICALLY_CHANGED;
+extern const isc::log::MessageID DHCP4_SUBNET_SELECTED;
+extern const isc::log::MessageID DHCP4_SUBNET_SELECTION_FAILED;
+extern const isc::log::MessageID DHCP4_TESTING_MODE_SEND_TO_SOURCE_ENABLED;
+extern const isc::log::MessageID DHCP4_UNKNOWN_ADDRESS_REQUESTED;
+extern const isc::log::MessageID DHCP6_DHCP4O6_PACKET_RECEIVED;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // DHCP4_MESSAGES_H
diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes
new file mode 100644
index 0000000..1356ed2
--- /dev/null
+++ b/src/bin/dhcp4/dhcp4_messages.mes
@@ -0,0 +1,953 @@
+# Copyright (C) 2012-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/.
+
+$NAMESPACE isc::dhcp
+
+% DHCP4_ACTIVATE_INTERFACE activating interface %1
+This message is printed when DHCPv4 server enabled an interface to be used
+to receive DHCPv4 traffic. IPv4 socket on this interface will be opened once
+Interface Manager starts up procedure of opening sockets.
+
+% DHCP4_ALREADY_RUNNING %1 already running? %2
+This is an error message that occurs when the DHCPv4 server encounters
+a pre-existing PID file which contains the PID of a running process.
+This most likely indicates an attempt to start a second instance of
+the server using the same configuration file. It is possible, though
+unlikely that the PID file is a remnant left behind by a server crash or
+power failure and the PID it contains refers to a process other than
+the server. In such an event, it would be necessary to manually remove
+the PID file. The first argument is the DHCPv4 process name, the
+second contains the PID and PID file.
+
+% DHCP4_BUFFER_RECEIVED received buffer from %1:%2 to %3:%4 over interface %5
+This debug message is logged when the server has received a packet
+over the socket. When the message is logged the contents of the received
+packet hasn't been parsed yet. The only available information is the
+interface and the source and destination IPv4 addresses/ports.
+
+% DHCP4_BUFFER_RECEIVE_FAIL error on attempt to receive packet: %1
+The DHCPv4 server tried to receive a packet but an error
+occurred during this attempt. The reason for the error is included in
+the message.
+
+% DHCP4_BUFFER_UNPACK parsing buffer received from %1 to %2 over interface %3
+This debug message is issued when the server starts parsing the received
+buffer holding the DHCPv4 message. The arguments specify the source and
+destination IPv4 addresses as well as the interface over which the buffer has
+been received.
+
+% DHCP4_BUFFER_WAIT_SIGNAL signal received while waiting for next packet
+This debug message is issued when the server was waiting for the
+packet, but the wait has been interrupted by the signal received
+by the process. The signal will be handled before the server starts
+waiting for next packets.
+
+% DHCP4_CB_ON_DEMAND_FETCH_UPDATES_FAIL error on demand attempt to fetch configuration updates from the configuration backend(s): %1
+This error message is issued when the server attempted to fetch
+configuration updates from the database and this on demand attempt failed.
+The sole argument which is returned to the config-backend-pull command
+caller too contains the reason for failure.
+
+% DHCP4_CB_PERIODIC_FETCH_UPDATES_FAIL error on periodic attempt to fetch configuration updates from the configuration backend(s): %1
+This error message is issued when the server attempted to fetch
+configuration updates from the database and this periodic attempt failed.
+The server will re-try according to the configured value of the
+config-fetch-wait-time parameter. The sole argument contains the
+reason for failure.
+
+% DHCP4_CB_PERIODIC_FETCH_UPDATES_RETRIES_EXHAUSTED maximum number of configuration fetch attempts: 10, has been exhausted without success
+This error indicates that the server has made a number of unsuccessful
+periodic attempts to fetch configuration updates from a configuration backend.
+The server will continue to operate but won't make any further attempts
+to fetch configuration updates. The administrator must fix the configuration
+in the database and reload (or restart) the server.
+
+% DHCP4_CLASS_ASSIGNED %1: client packet has been assigned to the following class(es): %2
+This debug message informs that incoming packet has been assigned to specified
+class or classes. This is a normal behavior and indicates successful operation.
+The first argument specifies the client and transaction identification
+information. The second argument includes all classes to which the
+packet has been assigned.
+
+% DHCP4_CLASS_UNCONFIGURED %1: client packet belongs to an unconfigured class: %2
+This debug message informs that incoming packet belongs to a class
+which cannot be found in the configuration. Either a hook written
+before the classification was added to Kea is used, or class naming is
+inconsistent.
+
+% DHCP4_CLASS_UNDEFINED required class %1 has no definition
+This debug message informs that a class is listed for required evaluation but
+has no definition.
+
+% DHCP4_CLASS_UNTESTABLE required class %1 has no test expression
+This debug message informs that a class was listed for required evaluation but
+its definition does not include a test expression to evaluate.
+
+% DHCP4_CLIENTID_IGNORED_FOR_LEASES %1: not using client identifier for lease allocation for subnet %2
+This debug message is issued when the server is processing the DHCPv4 message
+for which client identifier will not be used when allocating new lease or
+renewing existing lease. The server is explicitly configured to not use
+client identifier to lookup existing leases for the client and will not
+record client identifier in the lease database. This mode of operation
+is useful when clients don't use stable client identifiers, e.g. multi
+stage booting. The first argument includes the client and transaction
+identification information. The second argument specifies the identifier
+of the subnet where the client is connected and for which this mode of
+operation is configured on the server.
+
+% DHCP4_CLIENT_FQDN_DATA %1: Client sent FQDN option: %2
+This debug message includes the detailed information extracted from the
+Client FQDN option sent in the query. The first argument includes the
+client and transaction identification information. The second argument
+specifies the detailed information about the FQDN option received
+by the server.
+
+% DHCP4_CLIENT_FQDN_PROCESS %1: processing Client FQDN option
+This debug message is issued when the server starts processing the Client
+FQDN option sent in the client's query. The argument includes the
+client and transaction identification information.
+
+% DHCP4_CLIENT_HOSTNAME_DATA %1: client sent Hostname option: %2
+This debug message includes the detailed information extracted from the
+Hostname option sent in the query. The first argument includes the
+client and transaction identification information. The second argument
+specifies the hostname carried in the Hostname option sent by the
+client.
+
+% DHCP4_CLIENT_HOSTNAME_MALFORMED %1: client hostname option malformed: %2
+This debug message is issued when the DHCP server was unable to process the
+the hostname option sent by the client because the content is malformed.
+The first argument includes the client and transaction identification
+information. The second argument contains a description of the data error.
+
+% DHCP4_CLIENT_HOSTNAME_PROCESS %1: processing client's Hostname option
+This debug message is issued when the server starts processing the Hostname
+option sent in the client's query. The argument includes the client and
+transaction identification information.
+
+% DHCP4_CLIENT_NAME_PROC_FAIL %1: failed to process the fqdn or hostname sent by a client: %2
+This debug message is issued when the DHCP server was unable to process the
+FQDN or Hostname option sent by a client. This is likely because the client's
+name was malformed or due to internal server error. The first argument
+contains the client and transaction identification information. The
+second argument holds the detailed description of the error.
+
+% DHCP4_COMMAND_RECEIVED received command %1, arguments: %2
+A debug message listing the command (and possible arguments) received
+from the Kea control system by the DHCPv4 server.
+
+% DHCP4_CONFIG_COMPLETE DHCPv4 server has completed configuration: %1
+This is an informational message announcing the successful processing of a
+new configuration. It is output during server startup, and when an updated
+configuration is committed by the administrator. Additional information
+may be provided.
+
+% DHCP4_CONFIG_FETCH Fetching configuration data from config backends.
+This is an informational message emitted when the DHCPv4 server about to begin
+retrieving configuration data from one or more configuration backends.
+
+% DHCP4_CONFIG_LOAD_FAIL configuration error using file: %1, reason: %2
+This error message indicates that the DHCPv4 configuration has failed.
+If this is an initial configuration (during server's startup) the server
+will fail to start. If this is a dynamic reconfiguration attempt the
+server will continue to use an old configuration.
+
+% DHCP4_CONFIG_NEW_SUBNET a new subnet has been added to configuration: %1
+This is an informational message reporting that the configuration has
+been extended to include the specified IPv4 subnet.
+
+% DHCP4_CONFIG_OPTION_DUPLICATE multiple options with the code %1 added to the subnet %2
+This warning message is issued on an attempt to configure multiple options
+with the same option code for a particular subnet. Adding multiple options
+is uncommon for DHCPv4, but is not prohibited.
+
+% DHCP4_CONFIG_PACKET_QUEUE DHCPv4 packet queue info after configuration: %1
+This informational message is emitted during DHCPv4 server configuration,
+immediately after configuring the DHCPv4 packet queue. The information
+shown depends upon the packet queue type selected.
+
+% DHCP4_CONFIG_RECEIVED received configuration %1
+A debug message listing the configuration received by the DHCPv4 server.
+The source of that configuration depends on used configuration backend.
+
+% DHCP4_CONFIG_START DHCPv4 server is processing the following configuration: %1
+This is a debug message that is issued every time the server receives a
+configuration. That happens at start up and also when a server configuration
+change is committed by the administrator.
+
+% DHCP4_CONFIG_SYNTAX_WARNING configuration syntax warning: %1
+This warning message indicates that the DHCPv4 configuration had a minor
+syntax error. The error was displayed and the configuration parsing resumed.
+
+% DHCP4_CONFIG_UNRECOVERABLE_ERROR DHCPv4 server new configuration failed with an error which cannot be recovered
+This fatal error message is issued when a new configuration raised an error
+which cannot be recovered. A correct configuration must be applied as soon
+as possible as the server is no longer working.
+The configuration can be fixed in several ways. If the control channel
+is open, config-set with a valid configuration can be
+used. Alternatively, the original config file on disk could be fixed
+and SIGHUP signal could be sent (or the config-reload command
+issued). Finally, the server could be restarted completely.
+
+% DHCP4_CONFIG_UNSUPPORTED_OBJECT DHCPv4 server configuration includes an unsupported object: %1
+This error message is issued when the configuration includes an unsupported
+object (i.e. a top level element).
+
+% DHCP4_CONFIG_UPDATE updated configuration received: %1
+A debug message indicating that the DHCPv4 server has received an
+updated configuration from the Kea configuration system.
+
+% DHCP4_DB_RECONNECT_DISABLED database reconnect is disabled: max-reconnect-tries %1, reconnect-wait-time %2
+This is an informational message indicating that connectivity to either the
+lease or host database or both and that automatic reconnect is not enabled.
+
+% DHCP4_DB_RECONNECT_FAILED maximum number of database reconnect attempts: %1, has been exhausted without success
+This error indicates that the server failed to reconnect to the lease and/or
+host database(s) after making the maximum configured number of reconnect
+attempts. This might cause the server to shut down as specified in the
+configuration. Loss of connectivity is typically a network or database server
+issue.
+
+% DHCP4_DB_RECONNECT_LOST_CONNECTION database connection lost.
+This info message indicates that the connection has been lost and the dhcp
+service might have been disabled, as specified in the configuration, in order to
+try to recover the connection.
+
+% DHCP4_DB_RECONNECT_NO_DB_CTL unexpected error in database reconnect
+This is an error message indicating a programmatic error that should not
+occur. It prohibits the server from attempting to reconnect to its
+databases if connectivity is lost, and the server exits. This error
+should be reported.
+
+% DHCP4_DB_RECONNECT_SUCCEEDED database connection recovered.
+This info message indicates that the connection has been recovered and the dhcp
+service has been restored.
+
+% DHCP4_DDNS_REQUEST_SEND_FAILED failed sending a request to kea-dhcp-ddns, error: %1, ncr: %2
+This error message indicates that DHCP4 server attempted to send a DDNS
+update request to the DHCP-DDNS server. This is most likely a configuration or
+networking error.
+
+% DHCP4_DEACTIVATE_INTERFACE deactivate interface %1
+This message is printed when DHCPv4 server disables an interface from being
+used to receive DHCPv4 traffic. Sockets on this interface will not be opened
+by the Interface Manager until interface is enabled.
+
+% DHCP4_DECLINE_FAIL %1: error on decline lease for address %2: %3
+This error message indicates that the software failed to decline a
+lease from the lease database due to an error during a database
+operation. The first argument includes the client and the transaction
+identification information. The second argument holds the IPv4 address
+which decline was attempted. The last one contains the reason for
+failure.
+
+% DHCP4_DECLINE_LEASE Received DHCPDECLINE for addr %1 from client %2. The lease will be unavailable for %3 seconds.
+This informational message is printed when a client received an address, but
+discovered that it is being used by some other device and notified the server by
+sending a DHCPDECLINE message. The server checked that this address really was
+leased to the client and marked this address as unusable for a certain
+amount of time. This message may indicate a misconfiguration in a network,
+as there is either a buggy client or more likely a device that is using an
+address that it is not supposed to. The server will fully recover from this
+situation, but if the underlying problem of a misconfigured or rogue device
+is not solved, this address may be declined again in the future.
+
+% DHCP4_DECLINE_LEASE_MISMATCH Received DHCPDECLINE for addr %1 from client %2, but the data doesn't match: received hwaddr: %3, lease hwaddr: %4, received client-id: %5, lease client-id: %6
+This informational message means that a client attempted to report his address
+as declined (i.e. used by unknown entity). The server has information about
+a lease for that address, but the client's hardware address or client identifier
+does not match the server's stored information. The client's request will be ignored.
+
+% DHCP4_DECLINE_LEASE_NOT_FOUND Received DHCPDECLINE for addr %1 from client %2, but no such lease found.
+This warning message indicates that a client reported that his address was
+detected as a duplicate (i.e. another device in the network is using this address).
+However, the server does not have a record for this address. This may indicate
+a client's error or a server's purged database.
+
+% DHCP4_DEFERRED_OPTION_MISSING can find deferred option code %1 in the query
+This debug message is printed when a deferred option cannot be found in
+the query.
+
+% DHCP4_DEFERRED_OPTION_UNPACK_FAIL An error unpacking the deferred option %1: %2
+A debug message issued when deferred unpacking of an option failed, making it
+to be left unpacked in the packet. The first argument is the option code,
+the second the error.
+
+% DHCP4_DEVELOPMENT_VERSION This software is a development branch of Kea. It is not recommended for production use.
+This warning message is displayed when the version is a development
+(vs stable) one: the second number of the version is odd.
+
+% DHCP4_DHCP4O6_BAD_PACKET received malformed DHCPv4o6 packet: %1
+A malformed DHCPv4o6 packet was received.
+
+% DHCP4_DHCP4O6_PACKET_RECEIVED received DHCPv4o6 packet from DHCPv4 server (type %1) for %2 on interface %3
+This debug message is printed when the server is receiving a DHCPv4o6
+from the DHCPv4 server over inter-process communication.
+
+% DHCP4_DHCP4O6_PACKET_SEND %1: trying to send packet %2 (type %3) to %4 port %5 on interface %6 encapsulating %7: %8 (type %9)
+The arguments specify the client identification information (HW address
+and client identifier), DHCPv6 message name and type, source IPv6
+address and port, and interface name, DHCPv4 client identification,
+message name and type.
+
+% DHCP4_DHCP4O6_PACKET_SEND_FAIL %1: failed to send DHCPv4o6 packet: %2
+This error is output if the IPv4 DHCP server fails to send an
+DHCPv4o6 message to the IPv6 DHCP server. The reason for the
+error is included in the message.
+
+% DHCP4_DHCP4O6_RECEIVE_FAIL failed to receive DHCPv4o6: %1
+This debug message indicates the inter-process communication with the
+DHCPv6 server failed. The reason for the error is included in
+the message.
+
+% DHCP4_DHCP4O6_RECEIVING receiving DHCPv4o6 packet from DHCPv6 server
+This debug message is printed when the server is receiving a DHCPv4o6
+from the DHCPv6 server over inter-process communication socket.
+
+% DHCP4_DHCP4O6_RESPONSE_DATA %1: responding with packet %2 (type %3), packet details: %4
+A debug message including the detailed data about the packet being
+sent to the DHCPv6 server to be forwarded to the client. The first
+argument contains the client and the transaction identification
+information. The second and third argument contains the packet name
+and type respectively. The fourth argument contains detailed packet
+information.
+
+% DHCP4_DYNAMIC_RECONFIGURATION initiate server reconfiguration using file: %1, after receiving SIGHUP signal or config-reload command
+This is the info message logged when the DHCPv4 server starts reconfiguration
+as a result of receiving SIGHUP signal or config-reload command.
+
+% DHCP4_DYNAMIC_RECONFIGURATION_FAIL dynamic server reconfiguration failed with file: %1
+This is a fatal error message logged when the dynamic reconfiguration of the
+DHCP server failed.
+
+% DHCP4_DYNAMIC_RECONFIGURATION_SUCCESS dynamic server reconfiguration succeeded with file: %1
+This is info message logged when the dynamic reconfiguration of the DHCP server
+succeeded.
+
+% DHCP4_EMPTY_HOSTNAME %1: received empty hostname from the client, skipping processing of this option
+This debug message is issued when the server received an empty Hostname option
+from a client. Server does not process empty Hostname options and therefore
+option is skipped. The argument holds the client and transaction identification
+information.
+
+% DHCP4_FLEX_ID flexible identifier generated for incoming packet: %1
+This debug message is printed when host reservation type is set to flexible identifier
+and the expression specified in its configuration generated (was evaluated to)
+an identifier for incoming packet. This debug message is mainly intended as a
+debugging assistance for flexible identifier.
+
+% DHCP4_GENERATE_FQDN %1: client did not send a FQDN or hostname; FQDN will be generated for the client
+This debug message is issued when the server did not receive a Hostname option
+from the client and hostname generation is enabled. This provides a means to
+create DNS entries for unsophisticated clients.
+
+% DHCP4_HANDLE_SIGNAL_EXCEPTION An exception was thrown while handing signal: %1
+This error message is printed when an ISC or standard exception was raised during signal
+processing. This likely indicates a coding error and should be reported to ISC.
+
+% DHCP4_HOOKS_LIBS_RELOAD_FAIL reload of hooks libraries failed
+A "libreload" command was issued to reload the hooks libraries but for
+some reason the reload failed. Other error messages issued from the
+hooks framework will indicate the nature of the problem.
+
+% DHCP4_HOOK_BUFFER_RCVD_DROP received buffer from %1 to %2 over interface %3 was dropped because a callout set the drop flag
+This debug message is printed when a callout installed on buffer4_receive
+hook point set the drop flag. For this particular hook point, the
+setting of the flag by a callout instructs the server to drop the packet.
+The arguments specify the source and destination IPv4 address as well as
+the name of the interface over which the buffer has been received.
+
+% DHCP4_HOOK_BUFFER_RCVD_SKIP received buffer from %1 to %2 over interface %3 is not parsed because a callout set the next step to SKIP.
+This debug message is printed when a callout installed on
+buffer4_receive hook point set the next step to SKIP. For this particular hook
+point, this value set by a callout instructs the server to
+not parse the buffer because it was already parsed by the hook. The
+arguments specify the source and destination IPv4 address as well as
+the name of the interface over which the buffer has been received.
+
+% DHCP4_HOOK_BUFFER_SEND_SKIP %1: prepared response is dropped because a callout set the next step to SKIP.
+This debug message is printed when a callout installed on buffer4_send
+hook point set the next step to SKIP. For this particular hook point, the
+SKIP value set by a callout instructs the server to drop the packet.
+Server completed all the processing (e.g. may have assigned, updated
+or released leases), but the response will not be send to the client.
+
+% DHCP4_HOOK_DDNS_UPDATE A hook has updated the DDNS parameters: hostname %1=>%2, forward update %3=>%4, reverse update %5=>%6
+This message indicates that there was a hook called on ddns4_update hook point
+and that hook updated the DDNS update parameters: hostname, or whether to
+conduct forward (A record) or reverse (PTR record) DDNS updates.
+
+% DHCP4_HOOK_DECLINE_SKIP Decline4 hook callouts set status to DROP, ignoring packet.
+This message indicates that the server received DHCPDECLINE message, it was verified
+to be correct and matching server's lease information. The server called hooks
+for decline4 hook point and one of the callouts set next step status to DROP.
+The server will now abort processing of the packet as if it was never
+received. The lease will continue to be assigned to this client.
+
+% DHCP4_HOOK_LEASE4_RELEASE_SKIP %1: lease was not released because a callout set the next step to SKIP
+This debug message is printed when a callout installed on lease4_release
+hook point set the next step status to SKIP. For this particular hook point, the
+value set by a callout instructs the server to not release a lease.
+
+% DHCP4_HOOK_LEASES4_COMMITTED_DROP %1: packet is dropped, because a callout set the next step to DROP
+This debug message is printed when a callout installed on the leases4_committed
+hook point sets the next step to DROP.
+
+% DHCP4_HOOK_LEASES4_COMMITTED_PARK %1: packet is parked, because a callout set the next step to PARK
+This debug message is printed when a callout installed on the leases4_committed
+hook point sets the next step to PARK.
+
+% DHCP4_HOOK_LEASES4_PARKING_LOT_FULL The parked-packet-limit %1, has been reached, dropping query: %2
+This debug message occurs when the parking lot used to hold client queries
+while hook library work for them completes has reached or exceeded the
+limit set by the parked-packet-limit global parameter. This can occur when
+kea-dhcp4 is using hook libraries (e.g. HA) that implement the
+"leases4-committed" callout and client queries are arriving faster than
+those callouts can fulfill them.
+
+% DHCP4_HOOK_PACKET_RCVD_SKIP %1: packet is dropped, because a callout set the next step to SKIP
+This debug message is printed when a callout installed on the pkt4_receive
+hook point sets the next step to SKIP. For this particular hook point, the
+value setting of the flag instructs the server to drop the packet.
+
+% DHCP4_HOOK_PACKET_SEND_DROP %1: prepared DHCPv4 response was not sent because a callout set the next ste to DROP
+This debug message is printed when a callout installed on the pkt4_send
+hook point set the next step to DROP. For this particular hook point, the setting
+of the value by a callout instructs the server to drop the packet. This
+effectively means that the client will not get any response, even though
+the server processed client's request and acted on it (e.g. possibly
+allocated a lease). The argument specifies the client and transaction
+identification information.
+
+% DHCP4_HOOK_PACKET_SEND_SKIP %1: prepared response is not sent, because a callout set the next stp to SKIP
+This debug message is printed when a callout installed on the pkt4_send
+hook point sets the next step to SKIP. For this particular hook point, this
+setting instructs the server to drop the packet. This means that
+the client will not get any response, even though the server processed
+client's request and acted on it (e.g. possibly allocated a lease).
+
+% DHCP4_HOOK_SUBNET4_SELECT_DROP %1: packet was dropped, because a callout set the next step to 'drop'
+This debug message is printed when a callout installed on the
+subnet4_select hook point sets the next step to 'drop' value. For this particular hook
+point, the setting to that value instructs the server to drop the received
+packet. The argument specifies the client and transaction identification
+information.
+
+% DHCP4_HOOK_SUBNET4_SELECT_SKIP %1: no subnet was selected, because a callout set the next skip flag
+This debug message is printed when a callout installed on the
+subnet4_select hook point sets the next step to SKIP value. For this particular hook
+point, the setting of the flag instructs the server not to choose a
+subnet, an action that severely limits further processing; the server
+will be only able to offer global options - no addresses will be assigned.
+The argument specifies the client and transaction identification
+information.
+
+% DHCP4_INFORM_DIRECT_REPLY %1: DHCPACK in reply to the DHCPINFORM will be sent directly to %2 over %3
+This debug message is issued when the DHCPACK will be sent directly to the
+client, rather than via a relay. The first argument contains the client
+and transaction identification information. The second argument contains
+the client's IPv4 address to which the response will be sent. The third
+argument contains the local interface name.
+
+% DHCP4_INIT_FAIL failed to initialize Kea server: %1
+The server has failed to initialize. This may be because the configuration
+was not successful, or it encountered any other critical error on startup.
+Attached error message provides more details about the issue.
+
+% DHCP4_INIT_REBOOT %1: client is in INIT-REBOOT state and requests address %2
+This informational message is issued when the client is in the INIT-REBOOT
+state and is requesting an IPv4 address it is using to be allocated for it.
+The first argument includes the client and transaction identification
+information. The second argument specifies the requested IPv4 address.
+
+% DHCP4_LEASE_ADVERT %1: lease %2 will be advertised
+This informational message indicates that the server has found the lease to be
+offered to the client. It is up to the client to choose one server out of
+those which offered leases and continue allocation with that server.
+The first argument specifies the client and the transaction identification
+information. The second argument specifies the IPv4 address to be offered.
+
+% DHCP4_LEASE_ALLOC %1: lease %2 has been allocated for %3 seconds
+This informational message indicates that the server successfully granted a
+lease in response to client's DHCPREQUEST message. The lease information will
+be sent to the client in the DHCPACK message. The first argument contains the
+client and the transaction identification information. The second argument
+contains the allocated IPv4 address. The third argument is the validity
+lifetime.
+
+% DHCP4_LEASE_REUSE %1: lease %2 has been reused for %3 seconds
+This informational message indicates that the server successfully reused a
+lease in response to client's message. The lease information will
+be sent to the client in the DHCPACK message. The first argument contains the
+client and the transaction identification information. The second argument
+contains the allocated IPv4 address. The third argument is the validity
+lifetime.
+
+% DHCP4_MULTI_THREADING_INFO enabled: %1, number of threads: %2, queue size: %3
+This is a message listing some information about the multi-threading parameters
+with which the server is running.
+
+% DHCP4_NCR_CREATION_FAILED %1: failed to generate name change requests for DNS: %2
+This message indicates that server was unable to generate NameChangeRequests
+which should be sent to the kea-dhcp_ddns module to create
+new DNS records for the lease being acquired or to update existing records
+for the renewed lease. The first argument contains the client and transaction
+identification information. The second argument includes the reason for the
+failure.
+
+% DHCP4_NOT_RUNNING DHCPv4 server is not running
+A warning message is issued when an attempt is made to shut down the
+DHCPv4 server but it is not running.
+
+% DHCP4_NO_LEASE_INIT_REBOOT %1: no lease for address %2 requested by INIT-REBOOT client
+This debug message is issued when the client being in the INIT-REBOOT state
+requested an IPv4 address but this client is unknown. The server will not
+respond. The first argument includes the client and the transaction id
+identification information. The second argument includes the IPv4 address
+requested by the client.
+
+% DHCP4_NO_SOCKETS_OPEN no interface configured to listen to DHCP traffic
+This warning message is issued when current server configuration specifies
+no interfaces that server should listen on, or specified interfaces are not
+configured to receive the traffic.
+
+% DHCP4_OPEN_CONFIG_DB Opening configuration database: %1
+This message is printed when the DHCPv4 server is attempting to open a
+configuration database. The database access string with password redacted
+is logged.
+
+% DHCP4_OPEN_SOCKET opening service sockets on port %1
+A debug message issued during startup, this indicates that the DHCPv4
+server is about to open sockets on the specified port.
+
+% DHCP4_OPEN_SOCKETS_FAILED maximum number of open service sockets attempts: %1, has been exhausted without success
+This error indicates that the server failed to bind service sockets after making
+the maximum configured number of reconnect attempts. This might cause the server
+to shut down as specified in the configuration.
+
+% DHCP4_OPEN_SOCKETS_NO_RECONNECT_CTL unexpected error in bind service sockets.
+This is an error message indicating a programmatic error that should not occur.
+It prohibits the server from attempting to bind to its service sockets if they
+are unavailable, and the server exits. This error should be reported.
+
+% DHCP4_OPEN_SOCKET_FAIL failed to open socket: %1
+A warning message issued when IfaceMgr fails to open and bind a socket. The reason
+for the failure is appended as an argument of the log message.
+
+% DHCP4_PACKET_DROP_0001 failed to parse packet from %1 to %2, received over interface %3, reason: %4
+The DHCPv4 server has received a packet that it is unable to
+interpret. The reason why the packet is invalid is included in the message.
+
+% DHCP4_PACKET_DROP_0002 %1, from interface %2: no suitable subnet configured for a direct client
+This info message is logged when received a message from a directly connected
+client but there is no suitable subnet configured for the interface on
+which this message has been received. The IPv4 address assigned on this
+interface must belong to one of the configured subnets. Otherwise
+received message is dropped.
+
+% DHCP4_PACKET_DROP_0003 %1, from interface %2: it contains a foreign server identifier
+This debug message is issued when received DHCPv4 message is dropped because
+it is addressed to a different server, i.e. a server identifier held by
+this message doesn't match the identifier used by our server. The arguments
+of this message hold the name of the transaction id and interface on which
+the message has been received.
+
+% DHCP4_PACKET_DROP_0004 %1, from interface %2: missing msg-type option
+This is a debug message informing that incoming DHCPv4 packet did not
+have mandatory DHCP message type option and thus was dropped. The
+arguments specify the client and transaction identification information,
+as well as the interface on which the message has been received.
+
+% DHCP4_PACKET_DROP_0005 %1: unrecognized type %2 in option 53
+This debug message indicates that the message type carried in DHCPv4 option
+53 is unrecognized by the server. The valid message types are listed
+on the IANA website: http://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#message-type-53.
+The message will not be processed by the server. The arguments specify
+the client and transaction identification information, as well as the
+received message type.
+
+% DHCP4_PACKET_DROP_0006 %1: unsupported DHCPv4 message type %2
+This debug message indicates that the message type carried in DHCPv4 option
+53 is valid but the message will not be processed by the server. This includes
+messages being normally sent by the server to the client, such as DHCPOFFER,
+DHCPACK, DHCPNAK etc. The first argument specifies the client and transaction
+identification information. The second argument specifies the message type.
+
+% DHCP4_PACKET_DROP_0007 %1: failed to process packet: %2
+This is a general catch-all message indicating that the processing of a
+received packet failed. The reason is given in the message. The server
+will not send a response but will instead ignore the packet. The first
+argument contains the client and transaction identification information.
+The second argument includes the details of the error.
+
+% DHCP4_PACKET_DROP_0008 %1: DHCP service is globally disabled
+This debug message is issued when a packet is dropped because the DHCP service
+has been temporarily disabled. This affects all received DHCP packets. The
+service may be enabled by the "dhcp-enable" control command or automatically
+after a specified amount of time since receiving "dhcp-disable" command.
+
+% DHCP4_PACKET_DROP_0009 %1: Option 53 missing (no DHCP message type), is this a BOOTP packet?
+This debug message is issued when a packet is dropped because it did contain
+option 53 and thus has no DHCP message type. The most likely explanation is
+that it was BOOTP packet.
+
+% DHCP4_PACKET_DROP_0010 dropped as member of the special class 'DROP': %1
+This debug message is emitted when an incoming packet was classified
+into the special class 'DROP' and dropped. The packet details are displayed.
+
+% DHCP4_PACKET_DROP_0011 dropped as sent by the same client than a packet being processed by another thread: dropped %1 by thread %2 as duplicate of %3 processed by %4
+Currently multi-threading processing avoids races between packets sent by
+a client using the same client id option by dropping new packets until
+processing is finished.
+Packet details and thread identifiers are included for both packets in
+this warning message.
+
+% DHCP4_PACKET_DROP_0012 dropped as sent by the same client than a packet being processed by another thread: dropped %1 by thread %2 as duplicate of %3 processed by %4
+Currently multi-threading processing avoids races between packets sent by
+a client using the same hardware address by dropping new packets until
+processing is finished.
+Packet details and thread identifiers are included for both packets in
+this warning message.
+
+% DHCP4_PACKET_DROP_0013 dropped as member of the special class 'DROP' after host reservation lookup: %1
+This debug message is emitted when an incoming packet was classified
+after host reservation lookup into the special class 'DROP' and dropped.
+The packet details are displayed.
+
+% DHCP4_PACKET_DROP_0014 dropped as member of the special class 'DROP' after early global host reservations lookup: %1
+This debug message is emitted when an incoming packet was classified
+after early global host reservations lookup into the special class 'DROP'
+and dropped. The packet details are displayed.
+
+% DHCP4_PACKET_NAK_0001 %1: failed to select a subnet for incoming packet, src %2, type %3
+This error message is output when a packet was received from a subnet
+for which the DHCPv4 server has not been configured. The most probable
+cause is a misconfiguration of the server. The first argument contains
+the client and transaction identification information. The second argument
+contains the source IPv4 address of the packet. The third argument contains
+the name of the received packet.
+
+% DHCP4_PACKET_NAK_0002 %1: invalid address %2 requested by INIT-REBOOT
+This debug message is issued when the client being in the INIT-REBOOT state
+requested an IPv4 address which is not assigned to him. The server will respond
+to this client with DHCPNAK. The first argument contains the client and
+the transaction identification information. The second arguments holds the
+IPv4 address requested by the client.
+
+% DHCP4_PACKET_NAK_0003 %1: failed to advertise a lease, client sent ciaddr %2, requested-ip-address %3
+This message indicates that the server has failed to offer a lease to
+the specified client after receiving a DISCOVER message from it. There are
+many possible reasons for such a failure. The first argument contains
+the client and the transaction identification information. The second
+argument contains the IPv4 address in the ciaddr field. The third
+argument contains the IPv4 address in the requested-ip-address option
+(if present).
+
+% DHCP4_PACKET_NAK_0004 %1: failed to grant a lease, client sent ciaddr %2, requested-ip-address %3
+This message indicates that the server failed to grant a lease to the
+specified client after receiving a REQUEST message from it. There are many
+possible reasons for such a failure. Additional messages will indicate the
+reason. The first argument contains the client and the transaction
+identification information. The second argument contains the IPv4 address
+in the ciaddr field. The third argument contains the IPv4 address in the
+requested-ip-address option (if present).
+
+% DHCP4_PACKET_OPTIONS_SKIPPED An error unpacking an option, caused subsequent options to be skipped: %1
+A debug message issued when an option failed to unpack correctly, making it
+impossible to unpack the remaining options in the packet. The server will
+server will still attempt to service the packet.
+
+% DHCP4_PACKET_OPTION_UNPACK_FAIL An error unpacking the option %1: %2
+A debug message issued when an option failed to unpack correctly, making it
+to be left unpacked in the packet. The first argument is the option code,
+the second the error.
+
+% DHCP4_PACKET_PACK %1: preparing on-wire format of the packet to be sent
+This debug message is issued when the server starts preparing the on-wire
+format of the packet to be sent back to the client. The argument specifies
+the client and the transaction identification information.
+
+% DHCP4_PACKET_PACK_FAIL %1: preparing on-wire-format of the packet to be sent failed %2
+This error message is issued when preparing an on-wire format of the packet
+has failed. The first argument identifies the client and the DHCP transaction.
+The second argument includes the error string.
+
+% DHCP4_PACKET_PROCESS_EXCEPTION exception occurred during packet processing
+This error message indicates that a non-standard exception was raised
+during packet processing that was not caught by other, more specific
+exception handlers. This packet will be dropped and the server will
+continue operation.
+
+% DHCP4_PACKET_PROCESS_STD_EXCEPTION exception occurred during packet processing: %1
+This error message indicates that a standard exception was raised
+during packet processing that was not caught by other, more specific
+exception handlers. This packet will be dropped and the server will
+continue operation.
+
+% DHCP4_PACKET_QUEUE_FULL multi-threading packet queue is full
+A debug message noting that the multi-threading packet queue is full so
+the oldest packet of the queue was dropped to make room for the received one.
+
+% DHCP4_PACKET_RECEIVED %1: %2 (type %3) received from %4 to %5 on interface %6
+A debug message noting that the server has received the specified type of
+packet on the specified interface. The first argument specifies the
+client and transaction identification information. The second and third
+argument specify the name of the DHCPv4 message and its numeric type
+respectively. The remaining arguments specify the source IPv4 address,
+destination IPv4 address and the name of the interface on which the
+message has been received.
+
+% DHCP4_PACKET_SEND %1: trying to send packet %2 (type %3) from %4:%5 to %6:%7 on interface %8
+The arguments specify the client identification information (HW address
+and client identifier), DHCP message name and type, source IPv4
+address and port, destination IPv4 address and port and the
+interface name.
+
+This debug message is issued when the server is trying to send the
+response to the client. When the server is using an UDP socket
+to send the packet there are cases when this operation may be
+unsuccessful and no error message will be displayed. One such situation
+occurs when the server is unicasting the response to the 'ciaddr' of
+a DHCPINFORM message. This often requires broadcasting an ARP
+message to obtain the link layer address of the unicast destination.
+If broadcast ARP messages are blocked in the network, according to
+the firewall policy, the ARP message will not cause a response.
+Consequently, the response to the DHCPINFORM will not be sent.
+Since the ARP communication is under the OS control, Kea is not
+notified about the drop of the packet which it is trying to send
+and it has no means to display an error message.
+
+% DHCP4_PACKET_SEND_FAIL %1: failed to send DHCPv4 packet: %2
+This error is output if the DHCPv4 server fails to send an assembled
+DHCP message to a client. The first argument includes the client and
+the transaction identification information. The second argument includes
+the reason for failure.
+
+% DHCP4_PARSER_COMMIT_EXCEPTION parser failed to commit changes
+On receipt of message containing details to a change of the DHCPv4
+server configuration, a set of parsers were successfully created, but one
+of them failed to commit its changes due to a low-level system exception
+being raised. Additional messages may be output indicating the reason.
+
+% DHCP4_PARSER_COMMIT_FAIL parser failed to commit changes: %1
+On receipt of message containing details to a change of the DHCPv4
+server configuration, a set of parsers were successfully created, but
+one of them failed to commit its changes. The reason for the failure
+is given in the message.
+
+% DHCP4_PARSER_EXCEPTION failed to create or run parser for configuration element %1
+On receipt of message containing details to a change of its configuration,
+the DHCPv4 server failed to create a parser to decode the contents of
+the named configuration element, or the creation succeeded but the parsing
+actions and committal of changes failed. The message has been output in
+response to a non-Kea exception being raised. Additional messages
+may give further information.
+
+% DHCP4_PARSER_FAIL failed to create or run parser for configuration element %1: %2
+On receipt of message containing details to a change of its configuration,
+the DHCPv4 server failed to create a parser to decode the contents
+of the named configuration element, or the creation succeeded but the
+parsing actions and committal of changes failed. The reason for the
+failure is given in the message.
+
+% DHCP4_POST_ALLOCATION_NAME_UPDATE_FAIL %1: failed to update hostname %2 in a lease after address allocation: %3
+This message indicates the failure when trying to update the lease and/or
+options in the server's response with the hostname generated by the server
+or reserved for the client belonging to a shared network. The latter is
+the case when the server dynamically switches to another subnet (than
+initially selected for allocation) from the same shared network.
+
+% DHCP4_QUERY_DATA %1, packet details: %2
+A debug message printing the details of the received packet. The first
+argument includes the client and the transaction identification
+information.
+
+% DHCP4_RECLAIM_EXPIRED_LEASES_FAIL failed to reclaim expired leases: %1
+This error message indicates that the reclaim expired leases operation failed
+and provides the cause of failure.
+
+% DHCP4_RELEASE %1: address %2 was released properly.
+This informational message indicates that an address was released properly. It
+is a normal operation during client shutdown. The first argument includes
+the client and transaction identification information. The second argument
+includes the released IPv4 address.
+
+% DHCP4_RELEASE_EXCEPTION %1: while trying to release address %2 an exception occurred: %3
+This message is output when an error was encountered during an attempt
+to process a DHCPRELEASE message. The error will not affect the client,
+which does not expect any response from the server for DHCPRELEASE
+messages. Depending on the nature of problem, it may affect future
+server operation. The first argument includes the client and the
+transaction identification information. The second argument
+includes the IPv4 address which release was attempted. The last
+argument includes the detailed error description.
+
+% DHCP4_RELEASE_FAIL %1: failed to remove lease for address %2
+This error message indicates that the software failed to remove a
+lease from the lease database. It is probably due to an error during a
+database operation: resolution will most likely require administrator
+intervention (e.g. check if DHCP process has sufficient privileges to
+update the database). It may also be triggered if a lease was manually
+removed from the database during RELEASE message processing. The
+first argument includes the client and the transaction identification
+information. The second argument holds the IPv4 address which release
+was attempted.
+
+% DHCP4_RELEASE_FAIL_NO_LEASE %1: client is trying to release non-existing lease %2
+This debug message is printed when client attempts to release a lease,
+but no such lease is known to the server. The first argument contains
+the client and transaction identification information. The second
+argument contains the IPv4 address which the client is trying to
+release.
+
+% DHCP4_RELEASE_FAIL_WRONG_CLIENT %1: client is trying to release the lease %2 which belongs to a different client
+This debug message is issued when a client is trying to release the
+lease for the address which is currently used by another client, i.e.
+the 'client identifier' or 'chaddr' doesn't match between the client
+and the lease. The first argument includes the client and the
+transaction identification information. The second argument specifies
+the leased address.
+
+% DHCP4_RESERVATIONS_LOOKUP_FIRST_ENABLED Multi-threading is enabled and host reservations lookup is always performed first.
+This is a message informing that host reservations lookup is performed before
+lease lookup when multi-threading is enabled overwriting configured value.
+
+% DHCP4_RESERVED_HOSTNAME_ASSIGNED %1: server assigned reserved hostname %2
+This debug message is issued when the server found a hostname reservation
+for a client and uses this reservation in a hostname option sent back
+to this client. The reserved hostname is qualified with a value
+of 'qualifying-suffix' parameter, if this parameter is specified.
+
+% DHCP4_RESPONSE_DATA %1: responding with packet %2 (type %3), packet details: %4
+A debug message including the detailed data about the packet being sent
+to the client. The first argument contains the client and the transaction
+identification information. The second and third argument contains the
+packet name and type respectively. The fourth argument contains detailed
+packet information.
+
+% DHCP4_RESPONSE_FQDN_DATA %1: including FQDN option in the server's response: %2
+This debug message is issued when the server is adding the Client FQDN
+option in its response to the client. The first argument includes the
+client and transaction identification information. The second argument
+includes the details of the FQDN option being included. Note that the
+name carried in the FQDN option may be modified by the server when
+the lease is acquired for the client.
+
+% DHCP4_RESPONSE_HOSTNAME_DATA %1: including Hostname option in the server's response: %2
+This debug message is issued when the server is adding the Hostname
+option in its response to the client. The first argument includes the
+client and transaction identification information. The second argument
+includes the details of the FQDN option being included. Note that the
+name carried in the Hostname option may be modified by the server when
+the lease is acquired for the client.
+
+% DHCP4_RESPONSE_HOSTNAME_GENERATE %1: server has generated hostname %2 for the client
+This debug message includes the auto-generated hostname which will be used
+for the client which message is processed. Hostnames may need to be generated
+when required by the server's configuration or when the client hasn't
+supplied its hostname. The first argument includes the client and the
+transaction identification information. The second argument holds the
+generated hostname.
+
+% DHCP4_SERVER_FAILED server failed: %1
+The DHCPv4 server has encountered a fatal error and is terminating.
+The reason for the failure is included in the message.
+
+% DHCP4_SHUTDOWN server shutdown
+The DHCPv4 server has terminated normally.
+
+% DHCP4_SHUTDOWN_REQUEST shutdown of server requested
+This debug message indicates that a shutdown of the DHCPv4 server has
+been requested via a call to the 'shutdown' method of the core Dhcpv4Srv
+object.
+
+% DHCP4_SRV_CONSTRUCT_ERROR error creating Dhcpv4Srv object, reason: %1
+This error message indicates that during startup, the construction of a
+core component within the DHCPv4 server (the Dhcpv4 server object)
+has failed. As a result, the server will exit. The reason for the
+failure is given within the message.
+
+% DHCP4_SRV_D2STOP_ERROR error stopping IO with DHCP_DDNS during shutdown: %1
+This error message indicates that during shutdown, an error occurred while
+stopping IO between the DHCPv4 server and the DHCP_DDNS server. This is
+probably due to a programmatic error is not likely to impact either server
+upon restart. The reason for the failure is given within the message.
+
+% DHCP4_SRV_DHCP4O6_ERROR error stopping IO with DHCPv4o6 during shutdown: %1
+This error message indicates that during shutdown, an error occurred while
+stopping IO between the DHCPv4 server and the DHCPv6o6 server. This is
+probably due to a programmatic error is not likely to impact either server
+upon restart. The reason for the failure is given within the message.
+
+% DHCP4_SRV_UNLOAD_LIBRARIES_ERROR error unloading hooks libraries during shutdown: %1
+This error message indicates that during shutdown, unloading hooks
+libraries failed to close them. If the list of libraries is empty it is
+a programmatic error in the server code. If it is not empty it could be
+a programmatic error in one of the hooks libraries which could lead to
+a crash during finalization.
+
+% DHCP4_STARTED Kea DHCPv4 server version %1 started
+This informational message indicates that the DHCPv4 server has
+processed all configuration information and is ready to process
+DHCPv4 packets. The version is also printed.
+
+% DHCP4_STARTING Kea DHCPv4 server version %1 (%2) starting
+This informational message indicates that the DHCPv4 server has
+processed any command-line switches and is starting. The version
+is also printed.
+
+% DHCP4_START_INFO pid: %1, server port: %2, client port: %3, verbose: %4
+This is a debug message issued during the DHCPv4 server startup.
+It lists some information about the parameters with which the server
+is running.
+
+% DHCP4_SUBNET_DATA %1: the selected subnet details: %2
+This debug message includes the details of the subnet selected for
+the client. The first argument includes the client and the
+transaction identification information. The second arguments
+includes the subnet details.
+
+% DHCP4_SUBNET_DYNAMICALLY_CHANGED %1: changed selected subnet %2 to subnet %3 from shared network %4 for client assignments
+This debug message indicates that the server is using another subnet
+than initially selected for client assignments. This newly selected
+subnet belongs to the same shared network as the original subnet.
+Some reasons why the new subnet was selected include: address pool
+exhaustion in the original subnet or the fact that the new subnet
+includes some static reservations for this client.
+
+% DHCP4_SUBNET_SELECTED %1: the subnet with ID %2 was selected for client assignments
+This is a debug message noting the selection of a subnet to be used for
+address and option assignment. Subnet selection is one of the early
+steps in the processing of incoming client message. The first
+argument includes the client and the transaction identification
+information. The second argument holds the selected subnet id.
+
+% DHCP4_SUBNET_SELECTION_FAILED %1: failed to select subnet for the client
+This debug message indicates that the server failed to select the
+subnet for the client which has sent a message to the server.
+The server will not be able to offer any lease to the client and
+will drop its message if the received message was DHCPDISCOVER,
+and will send DHCPNAK if the received message was DHCPREQUEST.
+The argument includes the client and the transaction identification
+information.
+
+% DHCP4_TESTING_MODE_SEND_TO_SOURCE_ENABLED All packets will be send to source address of an incoming packet - use only for testing
+This message is printed then KEA_TEST_SEND_RESPONSES_TO_SOURCE
+environment variable is set. It's causing Kea to send packets to
+source address of incoming packet. Usable just in testing environment
+to simulate multiple subnet traffic from single source.
+
+% DHCP4_UNKNOWN_ADDRESS_REQUESTED %1: client requested an unknown address, client sent ciaddr %2, requested-ip-address %3
+This message indicates that the client requested an address that does
+not belong to any dynamic pools managed by this server. The first argument
+contains the client and the transaction identification information.
+The second argument contains the IPv4 address in the ciaddr field. The
+third argument contains the IPv4 address in the requested-ip-address
+option (if present).
+
+% DHCP6_DHCP4O6_PACKET_RECEIVED received DHCPv4o6 packet from DHCPv6 server (type %1) for %2 port %3 on interface %4
+This debug message is printed when the server is receiving a DHCPv4o6
+from the DHCPv6 server over inter-process communication.
diff --git a/src/bin/dhcp4/dhcp4_parser.cc b/src/bin/dhcp4/dhcp4_parser.cc
new file mode 100644
index 0000000..3255eb1
--- /dev/null
+++ b/src/bin/dhcp4/dhcp4_parser.cc
@@ -0,0 +1,6151 @@
+// A Bison parser, made by GNU Bison 3.8.2.
+
+// Skeleton implementation for Bison LALR(1) parsers in C++
+
+// Copyright (C) 2002-2015, 2018-2021 Free Software Foundation, Inc.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// As a special exception, you may create a larger work that contains
+// part or all of the Bison parser skeleton and distribute that work
+// under terms of your choice, so long as that work isn't itself a
+// parser generator using the skeleton or a modified version thereof
+// as a parser skeleton. Alternatively, if you modify or redistribute
+// the parser skeleton itself, you may (at your option) remove this
+// special exception, which will cause the skeleton and the resulting
+// Bison output files to be licensed under the GNU General Public
+// License without this special exception.
+
+// This special exception was added by the Free Software Foundation in
+// version 2.2 of Bison.
+
+// DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+// especially those whose name start with YY_ or yy_. They are
+// private implementation details that can be changed or removed.
+
+
+// Take the name prefix into account.
+#define yylex parser4_lex
+
+
+
+#include "dhcp4_parser.h"
+
+
+// Unqualified %code blocks.
+#line 34 "dhcp4_parser.yy"
+
+#include <dhcp4/parser_context.h>
+
+#line 52 "dhcp4_parser.cc"
+
+
+#ifndef YY_
+# if defined YYENABLE_NLS && YYENABLE_NLS
+# if ENABLE_NLS
+# include <libintl.h> // FIXME: INFRINGES ON USER NAME SPACE.
+# define YY_(msgid) dgettext ("bison-runtime", msgid)
+# endif
+# endif
+# ifndef YY_
+# define YY_(msgid) msgid
+# endif
+#endif
+
+
+// Whether we are compiled with exception support.
+#ifndef YY_EXCEPTIONS
+# if defined __GNUC__ && !defined __EXCEPTIONS
+# define YY_EXCEPTIONS 0
+# else
+# define YY_EXCEPTIONS 1
+# endif
+#endif
+
+#define YYRHSLOC(Rhs, K) ((Rhs)[K].location)
+/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N].
+ If N is 0, then set CURRENT to the empty location which ends
+ the previous symbol: RHS[0] (always defined). */
+
+# ifndef YYLLOC_DEFAULT
+# define YYLLOC_DEFAULT(Current, Rhs, N) \
+ do \
+ if (N) \
+ { \
+ (Current).begin = YYRHSLOC (Rhs, 1).begin; \
+ (Current).end = YYRHSLOC (Rhs, N).end; \
+ } \
+ else \
+ { \
+ (Current).begin = (Current).end = YYRHSLOC (Rhs, 0).end; \
+ } \
+ while (false)
+# endif
+
+
+// Enable debugging if requested.
+#if PARSER4_DEBUG
+
+// A pseudo ostream that takes yydebug_ into account.
+# define YYCDEBUG if (yydebug_) (*yycdebug_)
+
+# define YY_SYMBOL_PRINT(Title, Symbol) \
+ do { \
+ if (yydebug_) \
+ { \
+ *yycdebug_ << Title << ' '; \
+ yy_print_ (*yycdebug_, Symbol); \
+ *yycdebug_ << '\n'; \
+ } \
+ } while (false)
+
+# define YY_REDUCE_PRINT(Rule) \
+ do { \
+ if (yydebug_) \
+ yy_reduce_print_ (Rule); \
+ } while (false)
+
+# define YY_STACK_PRINT() \
+ do { \
+ if (yydebug_) \
+ yy_stack_print_ (); \
+ } while (false)
+
+#else // !PARSER4_DEBUG
+
+# define YYCDEBUG if (false) std::cerr
+# define YY_SYMBOL_PRINT(Title, Symbol) YY_USE (Symbol)
+# define YY_REDUCE_PRINT(Rule) static_cast<void> (0)
+# define YY_STACK_PRINT() static_cast<void> (0)
+
+#endif // !PARSER4_DEBUG
+
+#define yyerrok (yyerrstatus_ = 0)
+#define yyclearin (yyla.clear ())
+
+#define YYACCEPT goto yyacceptlab
+#define YYABORT goto yyabortlab
+#define YYERROR goto yyerrorlab
+#define YYRECOVERING() (!!yyerrstatus_)
+
+#line 14 "dhcp4_parser.yy"
+namespace isc { namespace dhcp {
+#line 145 "dhcp4_parser.cc"
+
+ /// Build a parser object.
+ Dhcp4Parser::Dhcp4Parser (isc::dhcp::Parser4Context& ctx_yyarg)
+#if PARSER4_DEBUG
+ : yydebug_ (false),
+ yycdebug_ (&std::cerr),
+#else
+ :
+#endif
+ ctx (ctx_yyarg)
+ {}
+
+ Dhcp4Parser::~Dhcp4Parser ()
+ {}
+
+ Dhcp4Parser::syntax_error::~syntax_error () YY_NOEXCEPT YY_NOTHROW
+ {}
+
+ /*---------.
+ | symbol. |
+ `---------*/
+
+
+
+ // by_state.
+ Dhcp4Parser::by_state::by_state () YY_NOEXCEPT
+ : state (empty_state)
+ {}
+
+ Dhcp4Parser::by_state::by_state (const by_state& that) YY_NOEXCEPT
+ : state (that.state)
+ {}
+
+ void
+ Dhcp4Parser::by_state::clear () YY_NOEXCEPT
+ {
+ state = empty_state;
+ }
+
+ void
+ Dhcp4Parser::by_state::move (by_state& that)
+ {
+ state = that.state;
+ that.clear ();
+ }
+
+ Dhcp4Parser::by_state::by_state (state_type s) YY_NOEXCEPT
+ : state (s)
+ {}
+
+ Dhcp4Parser::symbol_kind_type
+ Dhcp4Parser::by_state::kind () const YY_NOEXCEPT
+ {
+ if (state == empty_state)
+ return symbol_kind::S_YYEMPTY;
+ else
+ return YY_CAST (symbol_kind_type, yystos_[+state]);
+ }
+
+ Dhcp4Parser::stack_symbol_type::stack_symbol_type ()
+ {}
+
+ Dhcp4Parser::stack_symbol_type::stack_symbol_type (YY_RVREF (stack_symbol_type) that)
+ : super_type (YY_MOVE (that.state), YY_MOVE (that.location))
+ {
+ switch (that.kind ())
+ {
+ case symbol_kind::S_value: // value
+ case symbol_kind::S_map_value: // map_value
+ case symbol_kind::S_ddns_replace_client_name_value: // ddns_replace_client_name_value
+ case symbol_kind::S_socket_type: // socket_type
+ case symbol_kind::S_outbound_interface_value: // outbound_interface_value
+ case symbol_kind::S_db_type: // db_type
+ case symbol_kind::S_on_fail_mode: // on_fail_mode
+ case symbol_kind::S_hr_mode: // hr_mode
+ case symbol_kind::S_ncr_protocol_value: // ncr_protocol_value
+ value.YY_MOVE_OR_COPY< ElementPtr > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_BOOLEAN: // "boolean"
+ value.YY_MOVE_OR_COPY< bool > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_FLOAT: // "floating point"
+ value.YY_MOVE_OR_COPY< double > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_INTEGER: // "integer"
+ value.YY_MOVE_OR_COPY< int64_t > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ value.YY_MOVE_OR_COPY< std::string > (YY_MOVE (that.value));
+ break;
+
+ default:
+ break;
+ }
+
+#if 201103L <= YY_CPLUSPLUS
+ // that is emptied.
+ that.state = empty_state;
+#endif
+ }
+
+ Dhcp4Parser::stack_symbol_type::stack_symbol_type (state_type s, YY_MOVE_REF (symbol_type) that)
+ : super_type (s, YY_MOVE (that.location))
+ {
+ switch (that.kind ())
+ {
+ case symbol_kind::S_value: // value
+ case symbol_kind::S_map_value: // map_value
+ case symbol_kind::S_ddns_replace_client_name_value: // ddns_replace_client_name_value
+ case symbol_kind::S_socket_type: // socket_type
+ case symbol_kind::S_outbound_interface_value: // outbound_interface_value
+ case symbol_kind::S_db_type: // db_type
+ case symbol_kind::S_on_fail_mode: // on_fail_mode
+ case symbol_kind::S_hr_mode: // hr_mode
+ case symbol_kind::S_ncr_protocol_value: // ncr_protocol_value
+ value.move< ElementPtr > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_BOOLEAN: // "boolean"
+ value.move< bool > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_FLOAT: // "floating point"
+ value.move< double > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_INTEGER: // "integer"
+ value.move< int64_t > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ value.move< std::string > (YY_MOVE (that.value));
+ break;
+
+ default:
+ break;
+ }
+
+ // that is emptied.
+ that.kind_ = symbol_kind::S_YYEMPTY;
+ }
+
+#if YY_CPLUSPLUS < 201103L
+ Dhcp4Parser::stack_symbol_type&
+ Dhcp4Parser::stack_symbol_type::operator= (const stack_symbol_type& that)
+ {
+ state = that.state;
+ switch (that.kind ())
+ {
+ case symbol_kind::S_value: // value
+ case symbol_kind::S_map_value: // map_value
+ case symbol_kind::S_ddns_replace_client_name_value: // ddns_replace_client_name_value
+ case symbol_kind::S_socket_type: // socket_type
+ case symbol_kind::S_outbound_interface_value: // outbound_interface_value
+ case symbol_kind::S_db_type: // db_type
+ case symbol_kind::S_on_fail_mode: // on_fail_mode
+ case symbol_kind::S_hr_mode: // hr_mode
+ case symbol_kind::S_ncr_protocol_value: // ncr_protocol_value
+ value.copy< ElementPtr > (that.value);
+ break;
+
+ case symbol_kind::S_BOOLEAN: // "boolean"
+ value.copy< bool > (that.value);
+ break;
+
+ case symbol_kind::S_FLOAT: // "floating point"
+ value.copy< double > (that.value);
+ break;
+
+ case symbol_kind::S_INTEGER: // "integer"
+ value.copy< int64_t > (that.value);
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ value.copy< std::string > (that.value);
+ break;
+
+ default:
+ break;
+ }
+
+ location = that.location;
+ return *this;
+ }
+
+ Dhcp4Parser::stack_symbol_type&
+ Dhcp4Parser::stack_symbol_type::operator= (stack_symbol_type& that)
+ {
+ state = that.state;
+ switch (that.kind ())
+ {
+ case symbol_kind::S_value: // value
+ case symbol_kind::S_map_value: // map_value
+ case symbol_kind::S_ddns_replace_client_name_value: // ddns_replace_client_name_value
+ case symbol_kind::S_socket_type: // socket_type
+ case symbol_kind::S_outbound_interface_value: // outbound_interface_value
+ case symbol_kind::S_db_type: // db_type
+ case symbol_kind::S_on_fail_mode: // on_fail_mode
+ case symbol_kind::S_hr_mode: // hr_mode
+ case symbol_kind::S_ncr_protocol_value: // ncr_protocol_value
+ value.move< ElementPtr > (that.value);
+ break;
+
+ case symbol_kind::S_BOOLEAN: // "boolean"
+ value.move< bool > (that.value);
+ break;
+
+ case symbol_kind::S_FLOAT: // "floating point"
+ value.move< double > (that.value);
+ break;
+
+ case symbol_kind::S_INTEGER: // "integer"
+ value.move< int64_t > (that.value);
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ value.move< std::string > (that.value);
+ break;
+
+ default:
+ break;
+ }
+
+ location = that.location;
+ // that is emptied.
+ that.state = empty_state;
+ return *this;
+ }
+#endif
+
+ template <typename Base>
+ void
+ Dhcp4Parser::yy_destroy_ (const char* yymsg, basic_symbol<Base>& yysym) const
+ {
+ if (yymsg)
+ YY_SYMBOL_PRINT (yymsg, yysym);
+ }
+
+#if PARSER4_DEBUG
+ template <typename Base>
+ void
+ Dhcp4Parser::yy_print_ (std::ostream& yyo, const basic_symbol<Base>& yysym) const
+ {
+ std::ostream& yyoutput = yyo;
+ YY_USE (yyoutput);
+ if (yysym.empty ())
+ yyo << "empty symbol";
+ else
+ {
+ symbol_kind_type yykind = yysym.kind ();
+ yyo << (yykind < YYNTOKENS ? "token" : "nterm")
+ << ' ' << yysym.name () << " ("
+ << yysym.location << ": ";
+ switch (yykind)
+ {
+ case symbol_kind::S_STRING: // "constant string"
+#line 286 "dhcp4_parser.yy"
+ { yyoutput << yysym.value.template as < std::string > (); }
+#line 408 "dhcp4_parser.cc"
+ break;
+
+ case symbol_kind::S_INTEGER: // "integer"
+#line 286 "dhcp4_parser.yy"
+ { yyoutput << yysym.value.template as < int64_t > (); }
+#line 414 "dhcp4_parser.cc"
+ break;
+
+ case symbol_kind::S_FLOAT: // "floating point"
+#line 286 "dhcp4_parser.yy"
+ { yyoutput << yysym.value.template as < double > (); }
+#line 420 "dhcp4_parser.cc"
+ break;
+
+ case symbol_kind::S_BOOLEAN: // "boolean"
+#line 286 "dhcp4_parser.yy"
+ { yyoutput << yysym.value.template as < bool > (); }
+#line 426 "dhcp4_parser.cc"
+ break;
+
+ case symbol_kind::S_value: // value
+#line 286 "dhcp4_parser.yy"
+ { yyoutput << yysym.value.template as < ElementPtr > (); }
+#line 432 "dhcp4_parser.cc"
+ break;
+
+ case symbol_kind::S_map_value: // map_value
+#line 286 "dhcp4_parser.yy"
+ { yyoutput << yysym.value.template as < ElementPtr > (); }
+#line 438 "dhcp4_parser.cc"
+ break;
+
+ case symbol_kind::S_ddns_replace_client_name_value: // ddns_replace_client_name_value
+#line 286 "dhcp4_parser.yy"
+ { yyoutput << yysym.value.template as < ElementPtr > (); }
+#line 444 "dhcp4_parser.cc"
+ break;
+
+ case symbol_kind::S_socket_type: // socket_type
+#line 286 "dhcp4_parser.yy"
+ { yyoutput << yysym.value.template as < ElementPtr > (); }
+#line 450 "dhcp4_parser.cc"
+ break;
+
+ case symbol_kind::S_outbound_interface_value: // outbound_interface_value
+#line 286 "dhcp4_parser.yy"
+ { yyoutput << yysym.value.template as < ElementPtr > (); }
+#line 456 "dhcp4_parser.cc"
+ break;
+
+ case symbol_kind::S_db_type: // db_type
+#line 286 "dhcp4_parser.yy"
+ { yyoutput << yysym.value.template as < ElementPtr > (); }
+#line 462 "dhcp4_parser.cc"
+ break;
+
+ case symbol_kind::S_on_fail_mode: // on_fail_mode
+#line 286 "dhcp4_parser.yy"
+ { yyoutput << yysym.value.template as < ElementPtr > (); }
+#line 468 "dhcp4_parser.cc"
+ break;
+
+ case symbol_kind::S_hr_mode: // hr_mode
+#line 286 "dhcp4_parser.yy"
+ { yyoutput << yysym.value.template as < ElementPtr > (); }
+#line 474 "dhcp4_parser.cc"
+ break;
+
+ case symbol_kind::S_ncr_protocol_value: // ncr_protocol_value
+#line 286 "dhcp4_parser.yy"
+ { yyoutput << yysym.value.template as < ElementPtr > (); }
+#line 480 "dhcp4_parser.cc"
+ break;
+
+ default:
+ break;
+ }
+ yyo << ')';
+ }
+ }
+#endif
+
+ void
+ Dhcp4Parser::yypush_ (const char* m, YY_MOVE_REF (stack_symbol_type) sym)
+ {
+ if (m)
+ YY_SYMBOL_PRINT (m, sym);
+ yystack_.push (YY_MOVE (sym));
+ }
+
+ void
+ Dhcp4Parser::yypush_ (const char* m, state_type s, YY_MOVE_REF (symbol_type) sym)
+ {
+#if 201103L <= YY_CPLUSPLUS
+ yypush_ (m, stack_symbol_type (s, std::move (sym)));
+#else
+ stack_symbol_type ss (s, sym);
+ yypush_ (m, ss);
+#endif
+ }
+
+ void
+ Dhcp4Parser::yypop_ (int n) YY_NOEXCEPT
+ {
+ yystack_.pop (n);
+ }
+
+#if PARSER4_DEBUG
+ std::ostream&
+ Dhcp4Parser::debug_stream () const
+ {
+ return *yycdebug_;
+ }
+
+ void
+ Dhcp4Parser::set_debug_stream (std::ostream& o)
+ {
+ yycdebug_ = &o;
+ }
+
+
+ Dhcp4Parser::debug_level_type
+ Dhcp4Parser::debug_level () const
+ {
+ return yydebug_;
+ }
+
+ void
+ Dhcp4Parser::set_debug_level (debug_level_type l)
+ {
+ yydebug_ = l;
+ }
+#endif // PARSER4_DEBUG
+
+ Dhcp4Parser::state_type
+ Dhcp4Parser::yy_lr_goto_state_ (state_type yystate, int yysym)
+ {
+ int yyr = yypgoto_[yysym - YYNTOKENS] + yystate;
+ if (0 <= yyr && yyr <= yylast_ && yycheck_[yyr] == yystate)
+ return yytable_[yyr];
+ else
+ return yydefgoto_[yysym - YYNTOKENS];
+ }
+
+ bool
+ Dhcp4Parser::yy_pact_value_is_default_ (int yyvalue) YY_NOEXCEPT
+ {
+ return yyvalue == yypact_ninf_;
+ }
+
+ bool
+ Dhcp4Parser::yy_table_value_is_error_ (int yyvalue) YY_NOEXCEPT
+ {
+ return yyvalue == yytable_ninf_;
+ }
+
+ int
+ Dhcp4Parser::operator() ()
+ {
+ return parse ();
+ }
+
+ int
+ Dhcp4Parser::parse ()
+ {
+ int yyn;
+ /// Length of the RHS of the rule being reduced.
+ int yylen = 0;
+
+ // Error handling.
+ int yynerrs_ = 0;
+ int yyerrstatus_ = 0;
+
+ /// The lookahead symbol.
+ symbol_type yyla;
+
+ /// The locations where the error started and ended.
+ stack_symbol_type yyerror_range[3];
+
+ /// The return value of parse ().
+ int yyresult;
+
+#if YY_EXCEPTIONS
+ try
+#endif // YY_EXCEPTIONS
+ {
+ YYCDEBUG << "Starting parse\n";
+
+
+ /* Initialize the stack. The initial state will be set in
+ yynewstate, since the latter expects the semantical and the
+ location values to have been already stored, initialize these
+ stacks with a primary value. */
+ yystack_.clear ();
+ yypush_ (YY_NULLPTR, 0, YY_MOVE (yyla));
+
+ /*-----------------------------------------------.
+ | yynewstate -- push a new symbol on the stack. |
+ `-----------------------------------------------*/
+ yynewstate:
+ YYCDEBUG << "Entering state " << int (yystack_[0].state) << '\n';
+ YY_STACK_PRINT ();
+
+ // Accept?
+ if (yystack_[0].state == yyfinal_)
+ YYACCEPT;
+
+ goto yybackup;
+
+
+ /*-----------.
+ | yybackup. |
+ `-----------*/
+ yybackup:
+ // Try to take a decision without lookahead.
+ yyn = yypact_[+yystack_[0].state];
+ if (yy_pact_value_is_default_ (yyn))
+ goto yydefault;
+
+ // Read a lookahead token.
+ if (yyla.empty ())
+ {
+ YYCDEBUG << "Reading a token\n";
+#if YY_EXCEPTIONS
+ try
+#endif // YY_EXCEPTIONS
+ {
+ symbol_type yylookahead (yylex (ctx));
+ yyla.move (yylookahead);
+ }
+#if YY_EXCEPTIONS
+ catch (const syntax_error& yyexc)
+ {
+ YYCDEBUG << "Caught exception: " << yyexc.what() << '\n';
+ error (yyexc);
+ goto yyerrlab1;
+ }
+#endif // YY_EXCEPTIONS
+ }
+ YY_SYMBOL_PRINT ("Next token is", yyla);
+
+ if (yyla.kind () == symbol_kind::S_YYerror)
+ {
+ // The scanner already issued an error message, process directly
+ // to error recovery. But do not keep the error token as
+ // lookahead, it is too special and may lead us to an endless
+ // loop in error recovery. */
+ yyla.kind_ = symbol_kind::S_YYUNDEF;
+ goto yyerrlab1;
+ }
+
+ /* If the proper action on seeing token YYLA.TYPE is to reduce or
+ to detect an error, take that action. */
+ yyn += yyla.kind ();
+ if (yyn < 0 || yylast_ < yyn || yycheck_[yyn] != yyla.kind ())
+ {
+ goto yydefault;
+ }
+
+ // Reduce or error.
+ yyn = yytable_[yyn];
+ if (yyn <= 0)
+ {
+ if (yy_table_value_is_error_ (yyn))
+ goto yyerrlab;
+ yyn = -yyn;
+ goto yyreduce;
+ }
+
+ // Count tokens shifted since error; after three, turn off error status.
+ if (yyerrstatus_)
+ --yyerrstatus_;
+
+ // Shift the lookahead token.
+ yypush_ ("Shifting", state_type (yyn), YY_MOVE (yyla));
+ goto yynewstate;
+
+
+ /*-----------------------------------------------------------.
+ | yydefault -- do the default action for the current state. |
+ `-----------------------------------------------------------*/
+ yydefault:
+ yyn = yydefact_[+yystack_[0].state];
+ if (yyn == 0)
+ goto yyerrlab;
+ goto yyreduce;
+
+
+ /*-----------------------------.
+ | yyreduce -- do a reduction. |
+ `-----------------------------*/
+ yyreduce:
+ yylen = yyr2_[yyn];
+ {
+ stack_symbol_type yylhs;
+ yylhs.state = yy_lr_goto_state_ (yystack_[yylen].state, yyr1_[yyn]);
+ /* Variants are always initialized to an empty instance of the
+ correct type. The default '$$ = $1' action is NOT applied
+ when using variants. */
+ switch (yyr1_[yyn])
+ {
+ case symbol_kind::S_value: // value
+ case symbol_kind::S_map_value: // map_value
+ case symbol_kind::S_ddns_replace_client_name_value: // ddns_replace_client_name_value
+ case symbol_kind::S_socket_type: // socket_type
+ case symbol_kind::S_outbound_interface_value: // outbound_interface_value
+ case symbol_kind::S_db_type: // db_type
+ case symbol_kind::S_on_fail_mode: // on_fail_mode
+ case symbol_kind::S_hr_mode: // hr_mode
+ case symbol_kind::S_ncr_protocol_value: // ncr_protocol_value
+ yylhs.value.emplace< ElementPtr > ();
+ break;
+
+ case symbol_kind::S_BOOLEAN: // "boolean"
+ yylhs.value.emplace< bool > ();
+ break;
+
+ case symbol_kind::S_FLOAT: // "floating point"
+ yylhs.value.emplace< double > ();
+ break;
+
+ case symbol_kind::S_INTEGER: // "integer"
+ yylhs.value.emplace< int64_t > ();
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ yylhs.value.emplace< std::string > ();
+ break;
+
+ default:
+ break;
+ }
+
+
+ // Default location.
+ {
+ stack_type::slice range (yystack_, yylen);
+ YYLLOC_DEFAULT (yylhs.location, range, yylen);
+ yyerror_range[1].location = yylhs.location;
+ }
+
+ // Perform the reduction.
+ YY_REDUCE_PRINT (yyn);
+#if YY_EXCEPTIONS
+ try
+#endif // YY_EXCEPTIONS
+ {
+ switch (yyn)
+ {
+ case 2: // $@1: %empty
+#line 295 "dhcp4_parser.yy"
+ { ctx.ctx_ = ctx.NO_KEYWORD; }
+#line 761 "dhcp4_parser.cc"
+ break;
+
+ case 4: // $@2: %empty
+#line 296 "dhcp4_parser.yy"
+ { ctx.ctx_ = ctx.CONFIG; }
+#line 767 "dhcp4_parser.cc"
+ break;
+
+ case 6: // $@3: %empty
+#line 297 "dhcp4_parser.yy"
+ { ctx.ctx_ = ctx.DHCP4; }
+#line 773 "dhcp4_parser.cc"
+ break;
+
+ case 8: // $@4: %empty
+#line 298 "dhcp4_parser.yy"
+ { ctx.ctx_ = ctx.INTERFACES_CONFIG; }
+#line 779 "dhcp4_parser.cc"
+ break;
+
+ case 10: // $@5: %empty
+#line 299 "dhcp4_parser.yy"
+ { ctx.ctx_ = ctx.SUBNET4; }
+#line 785 "dhcp4_parser.cc"
+ break;
+
+ case 12: // $@6: %empty
+#line 300 "dhcp4_parser.yy"
+ { ctx.ctx_ = ctx.POOLS; }
+#line 791 "dhcp4_parser.cc"
+ break;
+
+ case 14: // $@7: %empty
+#line 301 "dhcp4_parser.yy"
+ { ctx.ctx_ = ctx.RESERVATIONS; }
+#line 797 "dhcp4_parser.cc"
+ break;
+
+ case 16: // $@8: %empty
+#line 302 "dhcp4_parser.yy"
+ { ctx.ctx_ = ctx.DHCP4; }
+#line 803 "dhcp4_parser.cc"
+ break;
+
+ case 18: // $@9: %empty
+#line 303 "dhcp4_parser.yy"
+ { ctx.ctx_ = ctx.OPTION_DEF; }
+#line 809 "dhcp4_parser.cc"
+ break;
+
+ case 20: // $@10: %empty
+#line 304 "dhcp4_parser.yy"
+ { ctx.ctx_ = ctx.OPTION_DATA; }
+#line 815 "dhcp4_parser.cc"
+ break;
+
+ case 22: // $@11: %empty
+#line 305 "dhcp4_parser.yy"
+ { ctx.ctx_ = ctx.HOOKS_LIBRARIES; }
+#line 821 "dhcp4_parser.cc"
+ break;
+
+ case 24: // $@12: %empty
+#line 306 "dhcp4_parser.yy"
+ { ctx.ctx_ = ctx.DHCP_DDNS; }
+#line 827 "dhcp4_parser.cc"
+ break;
+
+ case 26: // $@13: %empty
+#line 307 "dhcp4_parser.yy"
+ { ctx.ctx_ = ctx.CONFIG_CONTROL; }
+#line 833 "dhcp4_parser.cc"
+ break;
+
+ case 28: // value: "integer"
+#line 315 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location))); }
+#line 839 "dhcp4_parser.cc"
+ break;
+
+ case 29: // value: "floating point"
+#line 316 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new DoubleElement(yystack_[0].value.as < double > (), ctx.loc2pos(yystack_[0].location))); }
+#line 845 "dhcp4_parser.cc"
+ break;
+
+ case 30: // value: "boolean"
+#line 317 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location))); }
+#line 851 "dhcp4_parser.cc"
+ break;
+
+ case 31: // value: "constant string"
+#line 318 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location))); }
+#line 857 "dhcp4_parser.cc"
+ break;
+
+ case 32: // value: "null"
+#line 319 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new NullElement(ctx.loc2pos(yystack_[0].location))); }
+#line 863 "dhcp4_parser.cc"
+ break;
+
+ case 33: // value: map2
+#line 320 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ctx.stack_.back(); ctx.stack_.pop_back(); }
+#line 869 "dhcp4_parser.cc"
+ break;
+
+ case 34: // value: list_generic
+#line 321 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ctx.stack_.back(); ctx.stack_.pop_back(); }
+#line 875 "dhcp4_parser.cc"
+ break;
+
+ case 35: // sub_json: value
+#line 324 "dhcp4_parser.yy"
+ {
+ // Push back the JSON value on the stack
+ ctx.stack_.push_back(yystack_[0].value.as < ElementPtr > ());
+}
+#line 884 "dhcp4_parser.cc"
+ break;
+
+ case 36: // $@14: %empty
+#line 329 "dhcp4_parser.yy"
+ {
+ // This code is executed when we're about to start parsing
+ // the content of the map
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(m);
+}
+#line 895 "dhcp4_parser.cc"
+ break;
+
+ case 37: // map2: "{" $@14 map_content "}"
+#line 334 "dhcp4_parser.yy"
+ {
+ // map parsing completed. If we ever want to do any wrap up
+ // (maybe some sanity checking), this would be the best place
+ // for it.
+}
+#line 905 "dhcp4_parser.cc"
+ break;
+
+ case 38: // map_value: map2
+#line 340 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ctx.stack_.back(); ctx.stack_.pop_back(); }
+#line 911 "dhcp4_parser.cc"
+ break;
+
+ case 41: // not_empty_map: "constant string" ":" value
+#line 347 "dhcp4_parser.yy"
+ {
+ // map containing a single entry
+ ctx.unique(yystack_[2].value.as < std::string > (), ctx.loc2pos(yystack_[2].location));
+ ctx.stack_.back()->set(yystack_[2].value.as < std::string > (), yystack_[0].value.as < ElementPtr > ());
+ }
+#line 921 "dhcp4_parser.cc"
+ break;
+
+ case 42: // not_empty_map: not_empty_map "," "constant string" ":" value
+#line 352 "dhcp4_parser.yy"
+ {
+ // map consisting of a shorter map followed by
+ // comma and string:value
+ ctx.unique(yystack_[2].value.as < std::string > (), ctx.loc2pos(yystack_[2].location));
+ ctx.stack_.back()->set(yystack_[2].value.as < std::string > (), yystack_[0].value.as < ElementPtr > ());
+ }
+#line 932 "dhcp4_parser.cc"
+ break;
+
+ case 43: // not_empty_map: not_empty_map ","
+#line 358 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 940 "dhcp4_parser.cc"
+ break;
+
+ case 44: // $@15: %empty
+#line 363 "dhcp4_parser.yy"
+ {
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(l);
+}
+#line 949 "dhcp4_parser.cc"
+ break;
+
+ case 45: // list_generic: "[" $@15 list_content "]"
+#line 366 "dhcp4_parser.yy"
+ {
+ // list parsing complete. Put any sanity checking here
+}
+#line 957 "dhcp4_parser.cc"
+ break;
+
+ case 48: // not_empty_list: value
+#line 374 "dhcp4_parser.yy"
+ {
+ // List consisting of a single element.
+ ctx.stack_.back()->add(yystack_[0].value.as < ElementPtr > ());
+ }
+#line 966 "dhcp4_parser.cc"
+ break;
+
+ case 49: // not_empty_list: not_empty_list "," value
+#line 378 "dhcp4_parser.yy"
+ {
+ // List ending with , and a value.
+ ctx.stack_.back()->add(yystack_[0].value.as < ElementPtr > ());
+ }
+#line 975 "dhcp4_parser.cc"
+ break;
+
+ case 50: // not_empty_list: not_empty_list ","
+#line 382 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 983 "dhcp4_parser.cc"
+ break;
+
+ case 51: // $@16: %empty
+#line 388 "dhcp4_parser.yy"
+ {
+ // List parsing about to start
+}
+#line 991 "dhcp4_parser.cc"
+ break;
+
+ case 52: // list_strings: "[" $@16 list_strings_content "]"
+#line 390 "dhcp4_parser.yy"
+ {
+ // list parsing complete. Put any sanity checking here
+ //ctx.stack_.pop_back();
+}
+#line 1000 "dhcp4_parser.cc"
+ break;
+
+ case 55: // not_empty_list_strings: "constant string"
+#line 399 "dhcp4_parser.yy"
+ {
+ ElementPtr s(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(s);
+ }
+#line 1009 "dhcp4_parser.cc"
+ break;
+
+ case 56: // not_empty_list_strings: not_empty_list_strings "," "constant string"
+#line 403 "dhcp4_parser.yy"
+ {
+ ElementPtr s(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(s);
+ }
+#line 1018 "dhcp4_parser.cc"
+ break;
+
+ case 57: // not_empty_list_strings: not_empty_list_strings ","
+#line 407 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 1026 "dhcp4_parser.cc"
+ break;
+
+ case 58: // unknown_map_entry: "constant string" ":"
+#line 417 "dhcp4_parser.yy"
+ {
+ const std::string& where = ctx.contextName();
+ const std::string& keyword = yystack_[1].value.as < std::string > ();
+ error(yystack_[1].location,
+ "got unexpected keyword \"" + keyword + "\" in " + where + " map.");
+}
+#line 1037 "dhcp4_parser.cc"
+ break;
+
+ case 59: // $@17: %empty
+#line 426 "dhcp4_parser.yy"
+ {
+ // This code is executed when we're about to start parsing
+ // the content of the map
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(m);
+}
+#line 1048 "dhcp4_parser.cc"
+ break;
+
+ case 60: // syntax_map: "{" $@17 global_object "}"
+#line 431 "dhcp4_parser.yy"
+ {
+ // map parsing completed. If we ever want to do any wrap up
+ // (maybe some sanity checking), this would be the best place
+ // for it.
+
+ // Dhcp4 is required
+ ctx.require("Dhcp4", ctx.loc2pos(yystack_[3].location), ctx.loc2pos(yystack_[0].location));
+}
+#line 1061 "dhcp4_parser.cc"
+ break;
+
+ case 61: // $@18: %empty
+#line 441 "dhcp4_parser.yy"
+ {
+ // This code is executed when we're about to start parsing
+ // the content of the map
+ // Prevent against duplicate.
+ ctx.unique("Dhcp4", ctx.loc2pos(yystack_[0].location));
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("Dhcp4", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.DHCP4);
+}
+#line 1076 "dhcp4_parser.cc"
+ break;
+
+ case 62: // global_object: "Dhcp4" $@18 ":" "{" global_params "}"
+#line 450 "dhcp4_parser.yy"
+ {
+ // No global parameter is required
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 1086 "dhcp4_parser.cc"
+ break;
+
+ case 64: // global_object_comma: global_object ","
+#line 458 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+}
+#line 1094 "dhcp4_parser.cc"
+ break;
+
+ case 65: // $@19: %empty
+#line 464 "dhcp4_parser.yy"
+ {
+ // Parse the Dhcp4 map
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(m);
+}
+#line 1104 "dhcp4_parser.cc"
+ break;
+
+ case 66: // sub_dhcp4: "{" $@19 global_params "}"
+#line 468 "dhcp4_parser.yy"
+ {
+ // No global parameter is required
+ // parsing completed
+}
+#line 1113 "dhcp4_parser.cc"
+ break;
+
+ case 69: // global_params: global_params ","
+#line 475 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 1121 "dhcp4_parser.cc"
+ break;
+
+ case 134: // valid_lifetime: "valid-lifetime" ":" "integer"
+#line 548 "dhcp4_parser.yy"
+ {
+ ctx.unique("valid-lifetime", ctx.loc2pos(yystack_[2].location));
+ ElementPtr prf(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("valid-lifetime", prf);
+}
+#line 1131 "dhcp4_parser.cc"
+ break;
+
+ case 135: // min_valid_lifetime: "min-valid-lifetime" ":" "integer"
+#line 554 "dhcp4_parser.yy"
+ {
+ ctx.unique("min-valid-lifetime", ctx.loc2pos(yystack_[2].location));
+ ElementPtr prf(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("min-valid-lifetime", prf);
+}
+#line 1141 "dhcp4_parser.cc"
+ break;
+
+ case 136: // max_valid_lifetime: "max-valid-lifetime" ":" "integer"
+#line 560 "dhcp4_parser.yy"
+ {
+ ctx.unique("max-valid-lifetime", ctx.loc2pos(yystack_[2].location));
+ ElementPtr prf(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("max-valid-lifetime", prf);
+}
+#line 1151 "dhcp4_parser.cc"
+ break;
+
+ case 137: // renew_timer: "renew-timer" ":" "integer"
+#line 566 "dhcp4_parser.yy"
+ {
+ ctx.unique("renew-timer", ctx.loc2pos(yystack_[2].location));
+ ElementPtr prf(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("renew-timer", prf);
+}
+#line 1161 "dhcp4_parser.cc"
+ break;
+
+ case 138: // rebind_timer: "rebind-timer" ":" "integer"
+#line 572 "dhcp4_parser.yy"
+ {
+ ctx.unique("rebind-timer", ctx.loc2pos(yystack_[2].location));
+ ElementPtr prf(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("rebind-timer", prf);
+}
+#line 1171 "dhcp4_parser.cc"
+ break;
+
+ case 139: // calculate_tee_times: "calculate-tee-times" ":" "boolean"
+#line 578 "dhcp4_parser.yy"
+ {
+ ctx.unique("calculate-tee-times", ctx.loc2pos(yystack_[2].location));
+ ElementPtr ctt(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("calculate-tee-times", ctt);
+}
+#line 1181 "dhcp4_parser.cc"
+ break;
+
+ case 140: // t1_percent: "t1-percent" ":" "floating point"
+#line 584 "dhcp4_parser.yy"
+ {
+ ctx.unique("t1-percent", ctx.loc2pos(yystack_[2].location));
+ ElementPtr t1(new DoubleElement(yystack_[0].value.as < double > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("t1-percent", t1);
+}
+#line 1191 "dhcp4_parser.cc"
+ break;
+
+ case 141: // t2_percent: "t2-percent" ":" "floating point"
+#line 590 "dhcp4_parser.yy"
+ {
+ ctx.unique("t2-percent", ctx.loc2pos(yystack_[2].location));
+ ElementPtr t2(new DoubleElement(yystack_[0].value.as < double > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("t2-percent", t2);
+}
+#line 1201 "dhcp4_parser.cc"
+ break;
+
+ case 142: // cache_threshold: "cache-threshold" ":" "floating point"
+#line 596 "dhcp4_parser.yy"
+ {
+ ctx.unique("cache-threshold", ctx.loc2pos(yystack_[2].location));
+ ElementPtr ct(new DoubleElement(yystack_[0].value.as < double > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("cache-threshold", ct);
+}
+#line 1211 "dhcp4_parser.cc"
+ break;
+
+ case 143: // cache_max_age: "cache-max-age" ":" "integer"
+#line 602 "dhcp4_parser.yy"
+ {
+ ctx.unique("cache-max-age", ctx.loc2pos(yystack_[2].location));
+ ElementPtr cm(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("cache-max-age", cm);
+}
+#line 1221 "dhcp4_parser.cc"
+ break;
+
+ case 144: // decline_probation_period: "decline-probation-period" ":" "integer"
+#line 608 "dhcp4_parser.yy"
+ {
+ ctx.unique("decline-probation-period", ctx.loc2pos(yystack_[2].location));
+ ElementPtr dpp(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("decline-probation-period", dpp);
+}
+#line 1231 "dhcp4_parser.cc"
+ break;
+
+ case 145: // $@20: %empty
+#line 614 "dhcp4_parser.yy"
+ {
+ ctx.unique("server-tag", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1240 "dhcp4_parser.cc"
+ break;
+
+ case 146: // server_tag: "server-tag" $@20 ":" "constant string"
+#line 617 "dhcp4_parser.yy"
+ {
+ ElementPtr stag(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("server-tag", stag);
+ ctx.leave();
+}
+#line 1250 "dhcp4_parser.cc"
+ break;
+
+ case 147: // parked_packet_limit: "parked-packet-limit" ":" "integer"
+#line 623 "dhcp4_parser.yy"
+ {
+ ctx.unique("parked-packet-limit", ctx.loc2pos(yystack_[2].location));
+ ElementPtr ppl(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("parked-packet-limit", ppl);
+}
+#line 1260 "dhcp4_parser.cc"
+ break;
+
+ case 148: // echo_client_id: "echo-client-id" ":" "boolean"
+#line 629 "dhcp4_parser.yy"
+ {
+ ctx.unique("echo-client-id", ctx.loc2pos(yystack_[2].location));
+ ElementPtr echo(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("echo-client-id", echo);
+}
+#line 1270 "dhcp4_parser.cc"
+ break;
+
+ case 149: // match_client_id: "match-client-id" ":" "boolean"
+#line 635 "dhcp4_parser.yy"
+ {
+ ctx.unique("match-client-id", ctx.loc2pos(yystack_[2].location));
+ ElementPtr match(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("match-client-id", match);
+}
+#line 1280 "dhcp4_parser.cc"
+ break;
+
+ case 150: // authoritative: "authoritative" ":" "boolean"
+#line 641 "dhcp4_parser.yy"
+ {
+ ctx.unique("authoritative", ctx.loc2pos(yystack_[2].location));
+ ElementPtr prf(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("authoritative", prf);
+}
+#line 1290 "dhcp4_parser.cc"
+ break;
+
+ case 151: // ddns_send_updates: "ddns-send-updates" ":" "boolean"
+#line 647 "dhcp4_parser.yy"
+ {
+ ctx.unique("ddns-send-updates", ctx.loc2pos(yystack_[2].location));
+ ElementPtr b(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("ddns-send-updates", b);
+}
+#line 1300 "dhcp4_parser.cc"
+ break;
+
+ case 152: // ddns_override_no_update: "ddns-override-no-update" ":" "boolean"
+#line 653 "dhcp4_parser.yy"
+ {
+ ctx.unique("ddns-override-no-update", ctx.loc2pos(yystack_[2].location));
+ ElementPtr b(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("ddns-override-no-update", b);
+}
+#line 1310 "dhcp4_parser.cc"
+ break;
+
+ case 153: // ddns_override_client_update: "ddns-override-client-update" ":" "boolean"
+#line 659 "dhcp4_parser.yy"
+ {
+ ctx.unique("ddns-override-client-update", ctx.loc2pos(yystack_[2].location));
+ ElementPtr b(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("ddns-override-client-update", b);
+}
+#line 1320 "dhcp4_parser.cc"
+ break;
+
+ case 154: // $@21: %empty
+#line 665 "dhcp4_parser.yy"
+ {
+ ctx.enter(ctx.REPLACE_CLIENT_NAME);
+ ctx.unique("ddns-replace-client-name", ctx.loc2pos(yystack_[0].location));
+}
+#line 1329 "dhcp4_parser.cc"
+ break;
+
+ case 155: // ddns_replace_client_name: "ddns-replace-client-name" $@21 ":" ddns_replace_client_name_value
+#line 668 "dhcp4_parser.yy"
+ {
+ ctx.stack_.back()->set("ddns-replace-client-name", yystack_[0].value.as < ElementPtr > ());
+ ctx.leave();
+}
+#line 1338 "dhcp4_parser.cc"
+ break;
+
+ case 156: // ddns_replace_client_name_value: "when-present"
+#line 674 "dhcp4_parser.yy"
+ {
+ yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("when-present", ctx.loc2pos(yystack_[0].location)));
+ }
+#line 1346 "dhcp4_parser.cc"
+ break;
+
+ case 157: // ddns_replace_client_name_value: "never"
+#line 677 "dhcp4_parser.yy"
+ {
+ yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("never", ctx.loc2pos(yystack_[0].location)));
+ }
+#line 1354 "dhcp4_parser.cc"
+ break;
+
+ case 158: // ddns_replace_client_name_value: "always"
+#line 680 "dhcp4_parser.yy"
+ {
+ yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("always", ctx.loc2pos(yystack_[0].location)));
+ }
+#line 1362 "dhcp4_parser.cc"
+ break;
+
+ case 159: // ddns_replace_client_name_value: "when-not-present"
+#line 683 "dhcp4_parser.yy"
+ {
+ yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("when-not-present", ctx.loc2pos(yystack_[0].location)));
+ }
+#line 1370 "dhcp4_parser.cc"
+ break;
+
+ case 160: // ddns_replace_client_name_value: "boolean"
+#line 686 "dhcp4_parser.yy"
+ {
+ error(yystack_[0].location, "boolean values for the replace-client-name are "
+ "no longer supported");
+ }
+#line 1379 "dhcp4_parser.cc"
+ break;
+
+ case 161: // $@22: %empty
+#line 692 "dhcp4_parser.yy"
+ {
+ ctx.unique("ddns-generated-prefix", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1388 "dhcp4_parser.cc"
+ break;
+
+ case 162: // ddns_generated_prefix: "ddns-generated-prefix" $@22 ":" "constant string"
+#line 695 "dhcp4_parser.yy"
+ {
+ ElementPtr s(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("ddns-generated-prefix", s);
+ ctx.leave();
+}
+#line 1398 "dhcp4_parser.cc"
+ break;
+
+ case 163: // $@23: %empty
+#line 701 "dhcp4_parser.yy"
+ {
+ ctx.unique("ddns-qualifying-suffix", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1407 "dhcp4_parser.cc"
+ break;
+
+ case 164: // ddns_qualifying_suffix: "ddns-qualifying-suffix" $@23 ":" "constant string"
+#line 704 "dhcp4_parser.yy"
+ {
+ ElementPtr s(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("ddns-qualifying-suffix", s);
+ ctx.leave();
+}
+#line 1417 "dhcp4_parser.cc"
+ break;
+
+ case 165: // ddns_update_on_renew: "ddns-update-on-renew" ":" "boolean"
+#line 710 "dhcp4_parser.yy"
+ {
+ ctx.unique("ddns-update-on-renew", ctx.loc2pos(yystack_[2].location));
+ ElementPtr b(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("ddns-update-on-renew", b);
+}
+#line 1427 "dhcp4_parser.cc"
+ break;
+
+ case 166: // ddns_use_conflict_resolution: "ddns-use-conflict-resolution" ":" "boolean"
+#line 716 "dhcp4_parser.yy"
+ {
+ ctx.unique("ddns-use-conflict-resolution", ctx.loc2pos(yystack_[2].location));
+ ElementPtr b(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("ddns-use-conflict-resolution", b);
+}
+#line 1437 "dhcp4_parser.cc"
+ break;
+
+ case 167: // $@24: %empty
+#line 722 "dhcp4_parser.yy"
+ {
+ ctx.unique("hostname-char-set", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1446 "dhcp4_parser.cc"
+ break;
+
+ case 168: // hostname_char_set: "hostname-char-set" $@24 ":" "constant string"
+#line 725 "dhcp4_parser.yy"
+ {
+ ElementPtr s(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("hostname-char-set", s);
+ ctx.leave();
+}
+#line 1456 "dhcp4_parser.cc"
+ break;
+
+ case 169: // $@25: %empty
+#line 731 "dhcp4_parser.yy"
+ {
+ ctx.unique("hostname-char-replacement", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1465 "dhcp4_parser.cc"
+ break;
+
+ case 170: // hostname_char_replacement: "hostname-char-replacement" $@25 ":" "constant string"
+#line 734 "dhcp4_parser.yy"
+ {
+ ElementPtr s(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("hostname-char-replacement", s);
+ ctx.leave();
+}
+#line 1475 "dhcp4_parser.cc"
+ break;
+
+ case 171: // store_extended_info: "store-extended-info" ":" "boolean"
+#line 740 "dhcp4_parser.yy"
+ {
+ ctx.unique("store-extended-info", ctx.loc2pos(yystack_[2].location));
+ ElementPtr b(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("store-extended-info", b);
+}
+#line 1485 "dhcp4_parser.cc"
+ break;
+
+ case 172: // statistic_default_sample_count: "statistic-default-sample-count" ":" "integer"
+#line 746 "dhcp4_parser.yy"
+ {
+ ctx.unique("statistic-default-sample-count", ctx.loc2pos(yystack_[2].location));
+ ElementPtr count(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("statistic-default-sample-count", count);
+}
+#line 1495 "dhcp4_parser.cc"
+ break;
+
+ case 173: // statistic_default_sample_age: "statistic-default-sample-age" ":" "integer"
+#line 752 "dhcp4_parser.yy"
+ {
+ ctx.unique("statistic-default-sample-age", ctx.loc2pos(yystack_[2].location));
+ ElementPtr age(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("statistic-default-sample-age", age);
+}
+#line 1505 "dhcp4_parser.cc"
+ break;
+
+ case 174: // early_global_reservations_lookup: "early-global-reservations-lookup" ":" "boolean"
+#line 758 "dhcp4_parser.yy"
+ {
+ ctx.unique("early-global-reservations-lookup", ctx.loc2pos(yystack_[2].location));
+ ElementPtr early(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("early-global-reservations-lookup", early);
+}
+#line 1515 "dhcp4_parser.cc"
+ break;
+
+ case 175: // ip_reservations_unique: "ip-reservations-unique" ":" "boolean"
+#line 764 "dhcp4_parser.yy"
+ {
+ ctx.unique("ip-reservations-unique", ctx.loc2pos(yystack_[2].location));
+ ElementPtr unique(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("ip-reservations-unique", unique);
+}
+#line 1525 "dhcp4_parser.cc"
+ break;
+
+ case 176: // reservations_lookup_first: "reservations-lookup-first" ":" "boolean"
+#line 770 "dhcp4_parser.yy"
+ {
+ ctx.unique("reservations-lookup-first", ctx.loc2pos(yystack_[2].location));
+ ElementPtr first(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("reservations-lookup-first", first);
+}
+#line 1535 "dhcp4_parser.cc"
+ break;
+
+ case 177: // $@26: %empty
+#line 776 "dhcp4_parser.yy"
+ {
+ ctx.unique("interfaces-config", ctx.loc2pos(yystack_[0].location));
+ ElementPtr i(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("interfaces-config", i);
+ ctx.stack_.push_back(i);
+ ctx.enter(ctx.INTERFACES_CONFIG);
+}
+#line 1547 "dhcp4_parser.cc"
+ break;
+
+ case 178: // interfaces_config: "interfaces-config" $@26 ":" "{" interfaces_config_params "}"
+#line 782 "dhcp4_parser.yy"
+ {
+ // No interfaces config param is required
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 1557 "dhcp4_parser.cc"
+ break;
+
+ case 181: // interfaces_config_params: interfaces_config_params ","
+#line 790 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 1565 "dhcp4_parser.cc"
+ break;
+
+ case 192: // $@27: %empty
+#line 807 "dhcp4_parser.yy"
+ {
+ // Parse the interfaces-config map
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(m);
+}
+#line 1575 "dhcp4_parser.cc"
+ break;
+
+ case 193: // sub_interfaces4: "{" $@27 interfaces_config_params "}"
+#line 811 "dhcp4_parser.yy"
+ {
+ // No interfaces config param is required
+ // parsing completed
+}
+#line 1584 "dhcp4_parser.cc"
+ break;
+
+ case 194: // $@28: %empty
+#line 816 "dhcp4_parser.yy"
+ {
+ ctx.unique("interfaces", ctx.loc2pos(yystack_[0].location));
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("interfaces", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1596 "dhcp4_parser.cc"
+ break;
+
+ case 195: // interfaces_list: "interfaces" $@28 ":" list_strings
+#line 822 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 1605 "dhcp4_parser.cc"
+ break;
+
+ case 196: // $@29: %empty
+#line 827 "dhcp4_parser.yy"
+ {
+ ctx.unique("dhcp-socket-type", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.DHCP_SOCKET_TYPE);
+}
+#line 1614 "dhcp4_parser.cc"
+ break;
+
+ case 197: // dhcp_socket_type: "dhcp-socket-type" $@29 ":" socket_type
+#line 830 "dhcp4_parser.yy"
+ {
+ ctx.stack_.back()->set("dhcp-socket-type", yystack_[0].value.as < ElementPtr > ());
+ ctx.leave();
+}
+#line 1623 "dhcp4_parser.cc"
+ break;
+
+ case 198: // socket_type: "raw"
+#line 835 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("raw", ctx.loc2pos(yystack_[0].location))); }
+#line 1629 "dhcp4_parser.cc"
+ break;
+
+ case 199: // socket_type: "udp"
+#line 836 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("udp", ctx.loc2pos(yystack_[0].location))); }
+#line 1635 "dhcp4_parser.cc"
+ break;
+
+ case 200: // $@30: %empty
+#line 839 "dhcp4_parser.yy"
+ {
+ ctx.unique("outbound-interface", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.OUTBOUND_INTERFACE);
+}
+#line 1644 "dhcp4_parser.cc"
+ break;
+
+ case 201: // outbound_interface: "outbound-interface" $@30 ":" outbound_interface_value
+#line 842 "dhcp4_parser.yy"
+ {
+ ctx.stack_.back()->set("outbound-interface", yystack_[0].value.as < ElementPtr > ());
+ ctx.leave();
+}
+#line 1653 "dhcp4_parser.cc"
+ break;
+
+ case 202: // outbound_interface_value: "same-as-inbound"
+#line 847 "dhcp4_parser.yy"
+ {
+ yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("same-as-inbound", ctx.loc2pos(yystack_[0].location)));
+}
+#line 1661 "dhcp4_parser.cc"
+ break;
+
+ case 203: // outbound_interface_value: "use-routing"
+#line 849 "dhcp4_parser.yy"
+ {
+ yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("use-routing", ctx.loc2pos(yystack_[0].location)));
+ }
+#line 1669 "dhcp4_parser.cc"
+ break;
+
+ case 204: // re_detect: "re-detect" ":" "boolean"
+#line 853 "dhcp4_parser.yy"
+ {
+ ctx.unique("re-detect", ctx.loc2pos(yystack_[2].location));
+ ElementPtr b(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("re-detect", b);
+}
+#line 1679 "dhcp4_parser.cc"
+ break;
+
+ case 205: // service_sockets_require_all: "service-sockets-require-all" ":" "boolean"
+#line 859 "dhcp4_parser.yy"
+ {
+ ctx.unique("service-sockets-require-all", ctx.loc2pos(yystack_[2].location));
+ ElementPtr b(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("service-sockets-require-all", b);
+}
+#line 1689 "dhcp4_parser.cc"
+ break;
+
+ case 206: // service_sockets_retry_wait_time: "service-sockets-retry-wait-time" ":" "integer"
+#line 865 "dhcp4_parser.yy"
+ {
+ ctx.unique("service-sockets-retry-wait-time", ctx.loc2pos(yystack_[2].location));
+ ElementPtr n(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("service-sockets-retry-wait-time", n);
+}
+#line 1699 "dhcp4_parser.cc"
+ break;
+
+ case 207: // service_sockets_max_retries: "service-sockets-max-retries" ":" "integer"
+#line 871 "dhcp4_parser.yy"
+ {
+ ctx.unique("service-sockets-max-retries", ctx.loc2pos(yystack_[2].location));
+ ElementPtr n(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("service-sockets-max-retries", n);
+}
+#line 1709 "dhcp4_parser.cc"
+ break;
+
+ case 208: // $@31: %empty
+#line 877 "dhcp4_parser.yy"
+ {
+ ctx.unique("lease-database", ctx.loc2pos(yystack_[0].location));
+ ElementPtr i(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("lease-database", i);
+ ctx.stack_.push_back(i);
+ ctx.enter(ctx.LEASE_DATABASE);
+}
+#line 1721 "dhcp4_parser.cc"
+ break;
+
+ case 209: // lease_database: "lease-database" $@31 ":" "{" database_map_params "}"
+#line 883 "dhcp4_parser.yy"
+ {
+ // The type parameter is required
+ ctx.require("type", ctx.loc2pos(yystack_[2].location), ctx.loc2pos(yystack_[0].location));
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 1732 "dhcp4_parser.cc"
+ break;
+
+ case 210: // $@32: %empty
+#line 890 "dhcp4_parser.yy"
+ {
+ ctx.unique("sanity-checks", ctx.loc2pos(yystack_[0].location));
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("sanity-checks", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.SANITY_CHECKS);
+}
+#line 1744 "dhcp4_parser.cc"
+ break;
+
+ case 211: // sanity_checks: "sanity-checks" $@32 ":" "{" sanity_checks_params "}"
+#line 896 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 1753 "dhcp4_parser.cc"
+ break;
+
+ case 214: // sanity_checks_params: sanity_checks_params ","
+#line 903 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 1761 "dhcp4_parser.cc"
+ break;
+
+ case 216: // $@33: %empty
+#line 910 "dhcp4_parser.yy"
+ {
+ ctx.unique("lease-checks", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1770 "dhcp4_parser.cc"
+ break;
+
+ case 217: // lease_checks: "lease-checks" $@33 ":" "constant string"
+#line 913 "dhcp4_parser.yy"
+ {
+
+ if ( (string(yystack_[0].value.as < std::string > ()) == "none") ||
+ (string(yystack_[0].value.as < std::string > ()) == "warn") ||
+ (string(yystack_[0].value.as < std::string > ()) == "fix") ||
+ (string(yystack_[0].value.as < std::string > ()) == "fix-del") ||
+ (string(yystack_[0].value.as < std::string > ()) == "del")) {
+ ElementPtr user(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("lease-checks", user);
+ ctx.leave();
+ } else {
+ error(yystack_[0].location, "Unsupported 'lease-checks value: " + string(yystack_[0].value.as < std::string > ()) +
+ ", supported values are: none, warn, fix, fix-del, del");
+ }
+}
+#line 1790 "dhcp4_parser.cc"
+ break;
+
+ case 218: // $@34: %empty
+#line 929 "dhcp4_parser.yy"
+ {
+ ctx.unique("hosts-database", ctx.loc2pos(yystack_[0].location));
+ ElementPtr i(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("hosts-database", i);
+ ctx.stack_.push_back(i);
+ ctx.enter(ctx.HOSTS_DATABASE);
+}
+#line 1802 "dhcp4_parser.cc"
+ break;
+
+ case 219: // hosts_database: "hosts-database" $@34 ":" "{" database_map_params "}"
+#line 935 "dhcp4_parser.yy"
+ {
+ // The type parameter is required
+ ctx.require("type", ctx.loc2pos(yystack_[2].location), ctx.loc2pos(yystack_[0].location));
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 1813 "dhcp4_parser.cc"
+ break;
+
+ case 220: // $@35: %empty
+#line 942 "dhcp4_parser.yy"
+ {
+ ctx.unique("hosts-databases", ctx.loc2pos(yystack_[0].location));
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("hosts-databases", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.HOSTS_DATABASE);
+}
+#line 1825 "dhcp4_parser.cc"
+ break;
+
+ case 221: // hosts_databases: "hosts-databases" $@35 ":" "[" database_list "]"
+#line 948 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 1834 "dhcp4_parser.cc"
+ break;
+
+ case 226: // not_empty_database_list: not_empty_database_list ","
+#line 959 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 1842 "dhcp4_parser.cc"
+ break;
+
+ case 227: // $@36: %empty
+#line 964 "dhcp4_parser.yy"
+ {
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+}
+#line 1852 "dhcp4_parser.cc"
+ break;
+
+ case 228: // database: "{" $@36 database_map_params "}"
+#line 968 "dhcp4_parser.yy"
+ {
+ // The type parameter is required
+ ctx.require("type", ctx.loc2pos(yystack_[3].location), ctx.loc2pos(yystack_[0].location));
+ ctx.stack_.pop_back();
+}
+#line 1862 "dhcp4_parser.cc"
+ break;
+
+ case 231: // database_map_params: database_map_params ","
+#line 976 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 1870 "dhcp4_parser.cc"
+ break;
+
+ case 251: // $@37: %empty
+#line 1002 "dhcp4_parser.yy"
+ {
+ ctx.unique("type", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.DATABASE_TYPE);
+}
+#line 1879 "dhcp4_parser.cc"
+ break;
+
+ case 252: // database_type: "type" $@37 ":" db_type
+#line 1005 "dhcp4_parser.yy"
+ {
+ ctx.stack_.back()->set("type", yystack_[0].value.as < ElementPtr > ());
+ ctx.leave();
+}
+#line 1888 "dhcp4_parser.cc"
+ break;
+
+ case 253: // db_type: "memfile"
+#line 1010 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("memfile", ctx.loc2pos(yystack_[0].location))); }
+#line 1894 "dhcp4_parser.cc"
+ break;
+
+ case 254: // db_type: "mysql"
+#line 1011 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("mysql", ctx.loc2pos(yystack_[0].location))); }
+#line 1900 "dhcp4_parser.cc"
+ break;
+
+ case 255: // db_type: "postgresql"
+#line 1012 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("postgresql", ctx.loc2pos(yystack_[0].location))); }
+#line 1906 "dhcp4_parser.cc"
+ break;
+
+ case 256: // $@38: %empty
+#line 1015 "dhcp4_parser.yy"
+ {
+ ctx.unique("user", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1915 "dhcp4_parser.cc"
+ break;
+
+ case 257: // user: "user" $@38 ":" "constant string"
+#line 1018 "dhcp4_parser.yy"
+ {
+ ElementPtr user(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("user", user);
+ ctx.leave();
+}
+#line 1925 "dhcp4_parser.cc"
+ break;
+
+ case 258: // $@39: %empty
+#line 1024 "dhcp4_parser.yy"
+ {
+ ctx.unique("password", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1934 "dhcp4_parser.cc"
+ break;
+
+ case 259: // password: "password" $@39 ":" "constant string"
+#line 1027 "dhcp4_parser.yy"
+ {
+ ElementPtr pwd(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("password", pwd);
+ ctx.leave();
+}
+#line 1944 "dhcp4_parser.cc"
+ break;
+
+ case 260: // $@40: %empty
+#line 1033 "dhcp4_parser.yy"
+ {
+ ctx.unique("host", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1953 "dhcp4_parser.cc"
+ break;
+
+ case 261: // host: "host" $@40 ":" "constant string"
+#line 1036 "dhcp4_parser.yy"
+ {
+ ElementPtr h(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("host", h);
+ ctx.leave();
+}
+#line 1963 "dhcp4_parser.cc"
+ break;
+
+ case 262: // port: "port" ":" "integer"
+#line 1042 "dhcp4_parser.yy"
+ {
+ ctx.unique("port", ctx.loc2pos(yystack_[2].location));
+ ElementPtr p(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("port", p);
+}
+#line 1973 "dhcp4_parser.cc"
+ break;
+
+ case 263: // $@41: %empty
+#line 1048 "dhcp4_parser.yy"
+ {
+ ctx.unique("name", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1982 "dhcp4_parser.cc"
+ break;
+
+ case 264: // name: "name" $@41 ":" "constant string"
+#line 1051 "dhcp4_parser.yy"
+ {
+ ElementPtr name(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("name", name);
+ ctx.leave();
+}
+#line 1992 "dhcp4_parser.cc"
+ break;
+
+ case 265: // persist: "persist" ":" "boolean"
+#line 1057 "dhcp4_parser.yy"
+ {
+ ctx.unique("persist", ctx.loc2pos(yystack_[2].location));
+ ElementPtr n(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("persist", n);
+}
+#line 2002 "dhcp4_parser.cc"
+ break;
+
+ case 266: // lfc_interval: "lfc-interval" ":" "integer"
+#line 1063 "dhcp4_parser.yy"
+ {
+ ctx.unique("lfc-interval", ctx.loc2pos(yystack_[2].location));
+ ElementPtr n(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("lfc-interval", n);
+}
+#line 2012 "dhcp4_parser.cc"
+ break;
+
+ case 267: // readonly: "readonly" ":" "boolean"
+#line 1069 "dhcp4_parser.yy"
+ {
+ ctx.unique("readonly", ctx.loc2pos(yystack_[2].location));
+ ElementPtr n(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("readonly", n);
+}
+#line 2022 "dhcp4_parser.cc"
+ break;
+
+ case 268: // connect_timeout: "connect-timeout" ":" "integer"
+#line 1075 "dhcp4_parser.yy"
+ {
+ ctx.unique("connect-timeout", ctx.loc2pos(yystack_[2].location));
+ ElementPtr n(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("connect-timeout", n);
+}
+#line 2032 "dhcp4_parser.cc"
+ break;
+
+ case 269: // max_reconnect_tries: "max-reconnect-tries" ":" "integer"
+#line 1081 "dhcp4_parser.yy"
+ {
+ ctx.unique("max-reconnect-tries", ctx.loc2pos(yystack_[2].location));
+ ElementPtr n(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("max-reconnect-tries", n);
+}
+#line 2042 "dhcp4_parser.cc"
+ break;
+
+ case 270: // reconnect_wait_time: "reconnect-wait-time" ":" "integer"
+#line 1087 "dhcp4_parser.yy"
+ {
+ ctx.unique("reconnect-wait-time", ctx.loc2pos(yystack_[2].location));
+ ElementPtr n(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("reconnect-wait-time", n);
+}
+#line 2052 "dhcp4_parser.cc"
+ break;
+
+ case 271: // $@42: %empty
+#line 1093 "dhcp4_parser.yy"
+ {
+ ctx.unique("on-fail", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.DATABASE_ON_FAIL);
+}
+#line 2061 "dhcp4_parser.cc"
+ break;
+
+ case 272: // on_fail: "on-fail" $@42 ":" on_fail_mode
+#line 1096 "dhcp4_parser.yy"
+ {
+ ctx.stack_.back()->set("on-fail", yystack_[0].value.as < ElementPtr > ());
+ ctx.leave();
+}
+#line 2070 "dhcp4_parser.cc"
+ break;
+
+ case 273: // on_fail_mode: "stop-retry-exit"
+#line 1101 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("stop-retry-exit", ctx.loc2pos(yystack_[0].location))); }
+#line 2076 "dhcp4_parser.cc"
+ break;
+
+ case 274: // on_fail_mode: "serve-retry-exit"
+#line 1102 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("serve-retry-exit", ctx.loc2pos(yystack_[0].location))); }
+#line 2082 "dhcp4_parser.cc"
+ break;
+
+ case 275: // on_fail_mode: "serve-retry-continue"
+#line 1103 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("serve-retry-continue", ctx.loc2pos(yystack_[0].location))); }
+#line 2088 "dhcp4_parser.cc"
+ break;
+
+ case 276: // max_row_errors: "max-row-errors" ":" "integer"
+#line 1106 "dhcp4_parser.yy"
+ {
+ ctx.unique("max-row-errors", ctx.loc2pos(yystack_[2].location));
+ ElementPtr n(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("max-row-errors", n);
+}
+#line 2098 "dhcp4_parser.cc"
+ break;
+
+ case 277: // $@43: %empty
+#line 1112 "dhcp4_parser.yy"
+ {
+ ctx.unique("trust-anchor", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 2107 "dhcp4_parser.cc"
+ break;
+
+ case 278: // trust_anchor: "trust-anchor" $@43 ":" "constant string"
+#line 1115 "dhcp4_parser.yy"
+ {
+ ElementPtr ca(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("trust-anchor", ca);
+ ctx.leave();
+}
+#line 2117 "dhcp4_parser.cc"
+ break;
+
+ case 279: // $@44: %empty
+#line 1121 "dhcp4_parser.yy"
+ {
+ ctx.unique("cert-file", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 2126 "dhcp4_parser.cc"
+ break;
+
+ case 280: // cert_file: "cert-file" $@44 ":" "constant string"
+#line 1124 "dhcp4_parser.yy"
+ {
+ ElementPtr cert(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("cert-file", cert);
+ ctx.leave();
+}
+#line 2136 "dhcp4_parser.cc"
+ break;
+
+ case 281: // $@45: %empty
+#line 1130 "dhcp4_parser.yy"
+ {
+ ctx.unique("key-file", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 2145 "dhcp4_parser.cc"
+ break;
+
+ case 282: // key_file: "key-file" $@45 ":" "constant string"
+#line 1133 "dhcp4_parser.yy"
+ {
+ ElementPtr key(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("key-file", key);
+ ctx.leave();
+}
+#line 2155 "dhcp4_parser.cc"
+ break;
+
+ case 283: // $@46: %empty
+#line 1139 "dhcp4_parser.yy"
+ {
+ ctx.unique("cipher-list", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 2164 "dhcp4_parser.cc"
+ break;
+
+ case 284: // cipher_list: "cipher-list" $@46 ":" "constant string"
+#line 1142 "dhcp4_parser.yy"
+ {
+ ElementPtr cl(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("cipher-list", cl);
+ ctx.leave();
+}
+#line 2174 "dhcp4_parser.cc"
+ break;
+
+ case 285: // $@47: %empty
+#line 1148 "dhcp4_parser.yy"
+ {
+ ctx.unique("host-reservation-identifiers", ctx.loc2pos(yystack_[0].location));
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("host-reservation-identifiers", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.HOST_RESERVATION_IDENTIFIERS);
+}
+#line 2186 "dhcp4_parser.cc"
+ break;
+
+ case 286: // host_reservation_identifiers: "host-reservation-identifiers" $@47 ":" "[" host_reservation_identifiers_list "]"
+#line 1154 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 2195 "dhcp4_parser.cc"
+ break;
+
+ case 289: // host_reservation_identifiers_list: host_reservation_identifiers_list ","
+#line 1161 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 2203 "dhcp4_parser.cc"
+ break;
+
+ case 295: // duid_id: "duid"
+#line 1173 "dhcp4_parser.yy"
+ {
+ ElementPtr duid(new StringElement("duid", ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(duid);
+}
+#line 2212 "dhcp4_parser.cc"
+ break;
+
+ case 296: // hw_address_id: "hw-address"
+#line 1178 "dhcp4_parser.yy"
+ {
+ ElementPtr hwaddr(new StringElement("hw-address", ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(hwaddr);
+}
+#line 2221 "dhcp4_parser.cc"
+ break;
+
+ case 297: // circuit_id: "circuit-id"
+#line 1183 "dhcp4_parser.yy"
+ {
+ ElementPtr circuit(new StringElement("circuit-id", ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(circuit);
+}
+#line 2230 "dhcp4_parser.cc"
+ break;
+
+ case 298: // client_id: "client-id"
+#line 1188 "dhcp4_parser.yy"
+ {
+ ElementPtr client(new StringElement("client-id", ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(client);
+}
+#line 2239 "dhcp4_parser.cc"
+ break;
+
+ case 299: // flex_id: "flex-id"
+#line 1193 "dhcp4_parser.yy"
+ {
+ ElementPtr flex_id(new StringElement("flex-id", ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(flex_id);
+}
+#line 2248 "dhcp4_parser.cc"
+ break;
+
+ case 300: // $@48: %empty
+#line 1200 "dhcp4_parser.yy"
+ {
+ ctx.unique("multi-threading", ctx.loc2pos(yystack_[0].location));
+ ElementPtr mt(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("multi-threading", mt);
+ ctx.stack_.push_back(mt);
+ ctx.enter(ctx.DHCP_MULTI_THREADING);
+}
+#line 2260 "dhcp4_parser.cc"
+ break;
+
+ case 301: // dhcp_multi_threading: "multi-threading" $@48 ":" "{" multi_threading_params "}"
+#line 1206 "dhcp4_parser.yy"
+ {
+ // The enable parameter is required.
+ ctx.require("enable-multi-threading", ctx.loc2pos(yystack_[2].location), ctx.loc2pos(yystack_[0].location));
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 2271 "dhcp4_parser.cc"
+ break;
+
+ case 304: // multi_threading_params: multi_threading_params ","
+#line 1215 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 2279 "dhcp4_parser.cc"
+ break;
+
+ case 311: // enable_multi_threading: "enable-multi-threading" ":" "boolean"
+#line 1228 "dhcp4_parser.yy"
+ {
+ ctx.unique("enable-multi-threading", ctx.loc2pos(yystack_[2].location));
+ ElementPtr b(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("enable-multi-threading", b);
+}
+#line 2289 "dhcp4_parser.cc"
+ break;
+
+ case 312: // thread_pool_size: "thread-pool-size" ":" "integer"
+#line 1234 "dhcp4_parser.yy"
+ {
+ ctx.unique("thread-pool-size", ctx.loc2pos(yystack_[2].location));
+ ElementPtr prf(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("thread-pool-size", prf);
+}
+#line 2299 "dhcp4_parser.cc"
+ break;
+
+ case 313: // packet_queue_size: "packet-queue-size" ":" "integer"
+#line 1240 "dhcp4_parser.yy"
+ {
+ ctx.unique("packet-queue-size", ctx.loc2pos(yystack_[2].location));
+ ElementPtr prf(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("packet-queue-size", prf);
+}
+#line 2309 "dhcp4_parser.cc"
+ break;
+
+ case 314: // $@49: %empty
+#line 1246 "dhcp4_parser.yy"
+ {
+ ctx.unique("hooks-libraries", ctx.loc2pos(yystack_[0].location));
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("hooks-libraries", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.HOOKS_LIBRARIES);
+}
+#line 2321 "dhcp4_parser.cc"
+ break;
+
+ case 315: // hooks_libraries: "hooks-libraries" $@49 ":" "[" hooks_libraries_list "]"
+#line 1252 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 2330 "dhcp4_parser.cc"
+ break;
+
+ case 320: // not_empty_hooks_libraries_list: not_empty_hooks_libraries_list ","
+#line 1263 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 2338 "dhcp4_parser.cc"
+ break;
+
+ case 321: // $@50: %empty
+#line 1268 "dhcp4_parser.yy"
+ {
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+}
+#line 2348 "dhcp4_parser.cc"
+ break;
+
+ case 322: // hooks_library: "{" $@50 hooks_params "}"
+#line 1272 "dhcp4_parser.yy"
+ {
+ // The library hooks parameter is required
+ ctx.require("library", ctx.loc2pos(yystack_[3].location), ctx.loc2pos(yystack_[0].location));
+ ctx.stack_.pop_back();
+}
+#line 2358 "dhcp4_parser.cc"
+ break;
+
+ case 323: // $@51: %empty
+#line 1278 "dhcp4_parser.yy"
+ {
+ // Parse the hooks-libraries list entry map
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(m);
+}
+#line 2368 "dhcp4_parser.cc"
+ break;
+
+ case 324: // sub_hooks_library: "{" $@51 hooks_params "}"
+#line 1282 "dhcp4_parser.yy"
+ {
+ // The library hooks parameter is required
+ ctx.require("library", ctx.loc2pos(yystack_[3].location), ctx.loc2pos(yystack_[0].location));
+ // parsing completed
+}
+#line 2378 "dhcp4_parser.cc"
+ break;
+
+ case 327: // hooks_params: hooks_params ","
+#line 1290 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 2386 "dhcp4_parser.cc"
+ break;
+
+ case 331: // $@52: %empty
+#line 1300 "dhcp4_parser.yy"
+ {
+ ctx.unique("library", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 2395 "dhcp4_parser.cc"
+ break;
+
+ case 332: // library: "library" $@52 ":" "constant string"
+#line 1303 "dhcp4_parser.yy"
+ {
+ ElementPtr lib(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("library", lib);
+ ctx.leave();
+}
+#line 2405 "dhcp4_parser.cc"
+ break;
+
+ case 333: // $@53: %empty
+#line 1309 "dhcp4_parser.yy"
+ {
+ ctx.unique("parameters", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 2414 "dhcp4_parser.cc"
+ break;
+
+ case 334: // parameters: "parameters" $@53 ":" map_value
+#line 1312 "dhcp4_parser.yy"
+ {
+ ctx.stack_.back()->set("parameters", yystack_[0].value.as < ElementPtr > ());
+ ctx.leave();
+}
+#line 2423 "dhcp4_parser.cc"
+ break;
+
+ case 335: // $@54: %empty
+#line 1318 "dhcp4_parser.yy"
+ {
+ ctx.unique("expired-leases-processing", ctx.loc2pos(yystack_[0].location));
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("expired-leases-processing", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.EXPIRED_LEASES_PROCESSING);
+}
+#line 2435 "dhcp4_parser.cc"
+ break;
+
+ case 336: // expired_leases_processing: "expired-leases-processing" $@54 ":" "{" expired_leases_params "}"
+#line 1324 "dhcp4_parser.yy"
+ {
+ // No expired lease parameter is required
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 2445 "dhcp4_parser.cc"
+ break;
+
+ case 339: // expired_leases_params: expired_leases_params ","
+#line 1332 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 2453 "dhcp4_parser.cc"
+ break;
+
+ case 346: // reclaim_timer_wait_time: "reclaim-timer-wait-time" ":" "integer"
+#line 1345 "dhcp4_parser.yy"
+ {
+ ctx.unique("reclaim-timer-wait-time", ctx.loc2pos(yystack_[2].location));
+ ElementPtr value(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("reclaim-timer-wait-time", value);
+}
+#line 2463 "dhcp4_parser.cc"
+ break;
+
+ case 347: // flush_reclaimed_timer_wait_time: "flush-reclaimed-timer-wait-time" ":" "integer"
+#line 1351 "dhcp4_parser.yy"
+ {
+ ctx.unique("flush-reclaimed-timer-wait-time", ctx.loc2pos(yystack_[2].location));
+ ElementPtr value(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("flush-reclaimed-timer-wait-time", value);
+}
+#line 2473 "dhcp4_parser.cc"
+ break;
+
+ case 348: // hold_reclaimed_time: "hold-reclaimed-time" ":" "integer"
+#line 1357 "dhcp4_parser.yy"
+ {
+ ctx.unique("hold-reclaimed-time", ctx.loc2pos(yystack_[2].location));
+ ElementPtr value(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("hold-reclaimed-time", value);
+}
+#line 2483 "dhcp4_parser.cc"
+ break;
+
+ case 349: // max_reclaim_leases: "max-reclaim-leases" ":" "integer"
+#line 1363 "dhcp4_parser.yy"
+ {
+ ctx.unique("max-reclaim-leases", ctx.loc2pos(yystack_[2].location));
+ ElementPtr value(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("max-reclaim-leases", value);
+}
+#line 2493 "dhcp4_parser.cc"
+ break;
+
+ case 350: // max_reclaim_time: "max-reclaim-time" ":" "integer"
+#line 1369 "dhcp4_parser.yy"
+ {
+ ctx.unique("max-reclaim-time", ctx.loc2pos(yystack_[2].location));
+ ElementPtr value(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("max-reclaim-time", value);
+}
+#line 2503 "dhcp4_parser.cc"
+ break;
+
+ case 351: // unwarned_reclaim_cycles: "unwarned-reclaim-cycles" ":" "integer"
+#line 1375 "dhcp4_parser.yy"
+ {
+ ctx.unique("unwarned-reclaim-cycles", ctx.loc2pos(yystack_[2].location));
+ ElementPtr value(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("unwarned-reclaim-cycles", value);
+}
+#line 2513 "dhcp4_parser.cc"
+ break;
+
+ case 352: // $@55: %empty
+#line 1384 "dhcp4_parser.yy"
+ {
+ ctx.unique("subnet4", ctx.loc2pos(yystack_[0].location));
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("subnet4", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.SUBNET4);
+}
+#line 2525 "dhcp4_parser.cc"
+ break;
+
+ case 353: // subnet4_list: "subnet4" $@55 ":" "[" subnet4_list_content "]"
+#line 1390 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 2534 "dhcp4_parser.cc"
+ break;
+
+ case 358: // not_empty_subnet4_list: not_empty_subnet4_list ","
+#line 1404 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 2542 "dhcp4_parser.cc"
+ break;
+
+ case 359: // $@56: %empty
+#line 1413 "dhcp4_parser.yy"
+ {
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+}
+#line 2552 "dhcp4_parser.cc"
+ break;
+
+ case 360: // subnet4: "{" $@56 subnet4_params "}"
+#line 1417 "dhcp4_parser.yy"
+ {
+ // Once we reached this place, the subnet parsing is now complete.
+ // If we want to, we can implement default values here.
+ // In particular we can do things like this:
+ // if (!ctx.stack_.back()->get("interface")) {
+ // ctx.stack_.back()->set("interface", StringElement("loopback"));
+ // }
+ //
+ // We can also stack up one level (Dhcp4) and copy over whatever
+ // global parameters we want to:
+ // if (!ctx.stack_.back()->get("renew-timer")) {
+ // ElementPtr renew = ctx_stack_[...].get("renew-timer");
+ // if (renew) {
+ // ctx.stack_.back()->set("renew-timer", renew);
+ // }
+ // }
+
+ // The subnet subnet4 parameter is required
+ ctx.require("subnet", ctx.loc2pos(yystack_[3].location), ctx.loc2pos(yystack_[0].location));
+ ctx.stack_.pop_back();
+}
+#line 2578 "dhcp4_parser.cc"
+ break;
+
+ case 361: // $@57: %empty
+#line 1439 "dhcp4_parser.yy"
+ {
+ // Parse the subnet4 list entry map
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(m);
+}
+#line 2588 "dhcp4_parser.cc"
+ break;
+
+ case 362: // sub_subnet4: "{" $@57 subnet4_params "}"
+#line 1443 "dhcp4_parser.yy"
+ {
+ // The subnet subnet4 parameter is required
+ ctx.require("subnet", ctx.loc2pos(yystack_[3].location), ctx.loc2pos(yystack_[0].location));
+ // parsing completed
+}
+#line 2598 "dhcp4_parser.cc"
+ break;
+
+ case 365: // subnet4_params: subnet4_params ","
+#line 1452 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 2606 "dhcp4_parser.cc"
+ break;
+
+ case 411: // $@58: %empty
+#line 1505 "dhcp4_parser.yy"
+ {
+ ctx.unique("subnet", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 2615 "dhcp4_parser.cc"
+ break;
+
+ case 412: // subnet: "subnet" $@58 ":" "constant string"
+#line 1508 "dhcp4_parser.yy"
+ {
+ ElementPtr subnet(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("subnet", subnet);
+ ctx.leave();
+}
+#line 2625 "dhcp4_parser.cc"
+ break;
+
+ case 413: // $@59: %empty
+#line 1514 "dhcp4_parser.yy"
+ {
+ ctx.unique("4o6-interface", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 2634 "dhcp4_parser.cc"
+ break;
+
+ case 414: // subnet_4o6_interface: "4o6-interface" $@59 ":" "constant string"
+#line 1517 "dhcp4_parser.yy"
+ {
+ ElementPtr iface(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("4o6-interface", iface);
+ ctx.leave();
+}
+#line 2644 "dhcp4_parser.cc"
+ break;
+
+ case 415: // $@60: %empty
+#line 1523 "dhcp4_parser.yy"
+ {
+ ctx.unique("4o6-interface-id", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 2653 "dhcp4_parser.cc"
+ break;
+
+ case 416: // subnet_4o6_interface_id: "4o6-interface-id" $@60 ":" "constant string"
+#line 1526 "dhcp4_parser.yy"
+ {
+ ElementPtr iface(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("4o6-interface-id", iface);
+ ctx.leave();
+}
+#line 2663 "dhcp4_parser.cc"
+ break;
+
+ case 417: // $@61: %empty
+#line 1532 "dhcp4_parser.yy"
+ {
+ ctx.unique("4o6-subnet", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 2672 "dhcp4_parser.cc"
+ break;
+
+ case 418: // subnet_4o6_subnet: "4o6-subnet" $@61 ":" "constant string"
+#line 1535 "dhcp4_parser.yy"
+ {
+ ElementPtr iface(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("4o6-subnet", iface);
+ ctx.leave();
+}
+#line 2682 "dhcp4_parser.cc"
+ break;
+
+ case 419: // $@62: %empty
+#line 1541 "dhcp4_parser.yy"
+ {
+ ctx.unique("interface", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 2691 "dhcp4_parser.cc"
+ break;
+
+ case 420: // interface: "interface" $@62 ":" "constant string"
+#line 1544 "dhcp4_parser.yy"
+ {
+ ElementPtr iface(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("interface", iface);
+ ctx.leave();
+}
+#line 2701 "dhcp4_parser.cc"
+ break;
+
+ case 421: // $@63: %empty
+#line 1550 "dhcp4_parser.yy"
+ {
+ ctx.unique("client-class", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 2710 "dhcp4_parser.cc"
+ break;
+
+ case 422: // client_class: "client-class" $@63 ":" "constant string"
+#line 1553 "dhcp4_parser.yy"
+ {
+ ElementPtr cls(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("client-class", cls);
+ ctx.leave();
+}
+#line 2720 "dhcp4_parser.cc"
+ break;
+
+ case 423: // $@64: %empty
+#line 1559 "dhcp4_parser.yy"
+ {
+ ctx.unique("require-client-classes", ctx.loc2pos(yystack_[0].location));
+ ElementPtr c(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("require-client-classes", c);
+ ctx.stack_.push_back(c);
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 2732 "dhcp4_parser.cc"
+ break;
+
+ case 424: // require_client_classes: "require-client-classes" $@64 ":" list_strings
+#line 1565 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 2741 "dhcp4_parser.cc"
+ break;
+
+ case 425: // reservations_global: "reservations-global" ":" "boolean"
+#line 1570 "dhcp4_parser.yy"
+ {
+ ctx.unique("reservations-global", ctx.loc2pos(yystack_[2].location));
+ ElementPtr b(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("reservations-global", b);
+}
+#line 2751 "dhcp4_parser.cc"
+ break;
+
+ case 426: // reservations_in_subnet: "reservations-in-subnet" ":" "boolean"
+#line 1576 "dhcp4_parser.yy"
+ {
+ ctx.unique("reservations-in-subnet", ctx.loc2pos(yystack_[2].location));
+ ElementPtr b(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("reservations-in-subnet", b);
+}
+#line 2761 "dhcp4_parser.cc"
+ break;
+
+ case 427: // reservations_out_of_pool: "reservations-out-of-pool" ":" "boolean"
+#line 1582 "dhcp4_parser.yy"
+ {
+ ctx.unique("reservations-out-of-pool", ctx.loc2pos(yystack_[2].location));
+ ElementPtr b(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("reservations-out-of-pool", b);
+}
+#line 2771 "dhcp4_parser.cc"
+ break;
+
+ case 428: // $@65: %empty
+#line 1588 "dhcp4_parser.yy"
+ {
+ ctx.unique("reservation-mode", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.RESERVATION_MODE);
+}
+#line 2780 "dhcp4_parser.cc"
+ break;
+
+ case 429: // reservation_mode: "reservation-mode" $@65 ":" hr_mode
+#line 1591 "dhcp4_parser.yy"
+ {
+ ctx.stack_.back()->set("reservation-mode", yystack_[0].value.as < ElementPtr > ());
+ ctx.leave();
+}
+#line 2789 "dhcp4_parser.cc"
+ break;
+
+ case 430: // hr_mode: "disabled"
+#line 1596 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("disabled", ctx.loc2pos(yystack_[0].location))); }
+#line 2795 "dhcp4_parser.cc"
+ break;
+
+ case 431: // hr_mode: "out-of-pool"
+#line 1597 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("out-of-pool", ctx.loc2pos(yystack_[0].location))); }
+#line 2801 "dhcp4_parser.cc"
+ break;
+
+ case 432: // hr_mode: "global"
+#line 1598 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("global", ctx.loc2pos(yystack_[0].location))); }
+#line 2807 "dhcp4_parser.cc"
+ break;
+
+ case 433: // hr_mode: "all"
+#line 1599 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("all", ctx.loc2pos(yystack_[0].location))); }
+#line 2813 "dhcp4_parser.cc"
+ break;
+
+ case 434: // id: "id" ":" "integer"
+#line 1602 "dhcp4_parser.yy"
+ {
+ ctx.unique("id", ctx.loc2pos(yystack_[2].location));
+ ElementPtr id(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("id", id);
+}
+#line 2823 "dhcp4_parser.cc"
+ break;
+
+ case 435: // $@66: %empty
+#line 1610 "dhcp4_parser.yy"
+ {
+ ctx.unique("shared-networks", ctx.loc2pos(yystack_[0].location));
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("shared-networks", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.SHARED_NETWORK);
+}
+#line 2835 "dhcp4_parser.cc"
+ break;
+
+ case 436: // shared_networks: "shared-networks" $@66 ":" "[" shared_networks_content "]"
+#line 1616 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 2844 "dhcp4_parser.cc"
+ break;
+
+ case 441: // shared_networks_list: shared_networks_list ","
+#line 1629 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 2852 "dhcp4_parser.cc"
+ break;
+
+ case 442: // $@67: %empty
+#line 1634 "dhcp4_parser.yy"
+ {
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+}
+#line 2862 "dhcp4_parser.cc"
+ break;
+
+ case 443: // shared_network: "{" $@67 shared_network_params "}"
+#line 1638 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+}
+#line 2870 "dhcp4_parser.cc"
+ break;
+
+ case 446: // shared_network_params: shared_network_params ","
+#line 1644 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 2878 "dhcp4_parser.cc"
+ break;
+
+ case 487: // $@68: %empty
+#line 1695 "dhcp4_parser.yy"
+ {
+ ctx.unique("option-def", ctx.loc2pos(yystack_[0].location));
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("option-def", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.OPTION_DEF);
+}
+#line 2890 "dhcp4_parser.cc"
+ break;
+
+ case 488: // option_def_list: "option-def" $@68 ":" "[" option_def_list_content "]"
+#line 1701 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 2899 "dhcp4_parser.cc"
+ break;
+
+ case 489: // $@69: %empty
+#line 1709 "dhcp4_parser.yy"
+ {
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(m);
+}
+#line 2908 "dhcp4_parser.cc"
+ break;
+
+ case 490: // sub_option_def_list: "{" $@69 option_def_list "}"
+#line 1712 "dhcp4_parser.yy"
+ {
+ // parsing completed
+}
+#line 2916 "dhcp4_parser.cc"
+ break;
+
+ case 495: // not_empty_option_def_list: not_empty_option_def_list ","
+#line 1724 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 2924 "dhcp4_parser.cc"
+ break;
+
+ case 496: // $@70: %empty
+#line 1731 "dhcp4_parser.yy"
+ {
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+}
+#line 2934 "dhcp4_parser.cc"
+ break;
+
+ case 497: // option_def_entry: "{" $@70 option_def_params "}"
+#line 1735 "dhcp4_parser.yy"
+ {
+ // The name, code and type option def parameters are required.
+ ctx.require("name", ctx.loc2pos(yystack_[3].location), ctx.loc2pos(yystack_[0].location));
+ ctx.require("code", ctx.loc2pos(yystack_[3].location), ctx.loc2pos(yystack_[0].location));
+ ctx.require("type", ctx.loc2pos(yystack_[3].location), ctx.loc2pos(yystack_[0].location));
+ ctx.stack_.pop_back();
+}
+#line 2946 "dhcp4_parser.cc"
+ break;
+
+ case 498: // $@71: %empty
+#line 1746 "dhcp4_parser.yy"
+ {
+ // Parse the option-def list entry map
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(m);
+}
+#line 2956 "dhcp4_parser.cc"
+ break;
+
+ case 499: // sub_option_def: "{" $@71 option_def_params "}"
+#line 1750 "dhcp4_parser.yy"
+ {
+ // The name, code and type option def parameters are required.
+ ctx.require("name", ctx.loc2pos(yystack_[3].location), ctx.loc2pos(yystack_[0].location));
+ ctx.require("code", ctx.loc2pos(yystack_[3].location), ctx.loc2pos(yystack_[0].location));
+ ctx.require("type", ctx.loc2pos(yystack_[3].location), ctx.loc2pos(yystack_[0].location));
+ // parsing completed
+}
+#line 2968 "dhcp4_parser.cc"
+ break;
+
+ case 504: // not_empty_option_def_params: not_empty_option_def_params ","
+#line 1766 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 2976 "dhcp4_parser.cc"
+ break;
+
+ case 516: // code: "code" ":" "integer"
+#line 1785 "dhcp4_parser.yy"
+ {
+ ctx.unique("code", ctx.loc2pos(yystack_[2].location));
+ ElementPtr code(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("code", code);
+}
+#line 2986 "dhcp4_parser.cc"
+ break;
+
+ case 518: // $@72: %empty
+#line 1793 "dhcp4_parser.yy"
+ {
+ ctx.unique("type", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 2995 "dhcp4_parser.cc"
+ break;
+
+ case 519: // option_def_type: "type" $@72 ":" "constant string"
+#line 1796 "dhcp4_parser.yy"
+ {
+ ElementPtr prf(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("type", prf);
+ ctx.leave();
+}
+#line 3005 "dhcp4_parser.cc"
+ break;
+
+ case 520: // $@73: %empty
+#line 1802 "dhcp4_parser.yy"
+ {
+ ctx.unique("record-types", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3014 "dhcp4_parser.cc"
+ break;
+
+ case 521: // option_def_record_types: "record-types" $@73 ":" "constant string"
+#line 1805 "dhcp4_parser.yy"
+ {
+ ElementPtr rtypes(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("record-types", rtypes);
+ ctx.leave();
+}
+#line 3024 "dhcp4_parser.cc"
+ break;
+
+ case 522: // $@74: %empty
+#line 1811 "dhcp4_parser.yy"
+ {
+ ctx.unique("space", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3033 "dhcp4_parser.cc"
+ break;
+
+ case 523: // space: "space" $@74 ":" "constant string"
+#line 1814 "dhcp4_parser.yy"
+ {
+ ElementPtr space(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("space", space);
+ ctx.leave();
+}
+#line 3043 "dhcp4_parser.cc"
+ break;
+
+ case 525: // $@75: %empty
+#line 1822 "dhcp4_parser.yy"
+ {
+ ctx.unique("encapsulate", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3052 "dhcp4_parser.cc"
+ break;
+
+ case 526: // option_def_encapsulate: "encapsulate" $@75 ":" "constant string"
+#line 1825 "dhcp4_parser.yy"
+ {
+ ElementPtr encap(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("encapsulate", encap);
+ ctx.leave();
+}
+#line 3062 "dhcp4_parser.cc"
+ break;
+
+ case 527: // option_def_array: "array" ":" "boolean"
+#line 1831 "dhcp4_parser.yy"
+ {
+ ctx.unique("array", ctx.loc2pos(yystack_[2].location));
+ ElementPtr array(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("array", array);
+}
+#line 3072 "dhcp4_parser.cc"
+ break;
+
+ case 528: // $@76: %empty
+#line 1841 "dhcp4_parser.yy"
+ {
+ ctx.unique("option-data", ctx.loc2pos(yystack_[0].location));
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("option-data", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.OPTION_DATA);
+}
+#line 3084 "dhcp4_parser.cc"
+ break;
+
+ case 529: // option_data_list: "option-data" $@76 ":" "[" option_data_list_content "]"
+#line 1847 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 3093 "dhcp4_parser.cc"
+ break;
+
+ case 534: // not_empty_option_data_list: not_empty_option_data_list ","
+#line 1862 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 3101 "dhcp4_parser.cc"
+ break;
+
+ case 535: // $@77: %empty
+#line 1869 "dhcp4_parser.yy"
+ {
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+}
+#line 3111 "dhcp4_parser.cc"
+ break;
+
+ case 536: // option_data_entry: "{" $@77 option_data_params "}"
+#line 1873 "dhcp4_parser.yy"
+ {
+ /// @todo: the code or name parameters are required.
+ ctx.stack_.pop_back();
+}
+#line 3120 "dhcp4_parser.cc"
+ break;
+
+ case 537: // $@78: %empty
+#line 1881 "dhcp4_parser.yy"
+ {
+ // Parse the option-data list entry map
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(m);
+}
+#line 3130 "dhcp4_parser.cc"
+ break;
+
+ case 538: // sub_option_data: "{" $@78 option_data_params "}"
+#line 1885 "dhcp4_parser.yy"
+ {
+ /// @todo: the code or name parameters are required.
+ // parsing completed
+}
+#line 3139 "dhcp4_parser.cc"
+ break;
+
+ case 543: // not_empty_option_data_params: not_empty_option_data_params ","
+#line 1901 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 3147 "dhcp4_parser.cc"
+ break;
+
+ case 554: // $@79: %empty
+#line 1921 "dhcp4_parser.yy"
+ {
+ ctx.unique("data", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3156 "dhcp4_parser.cc"
+ break;
+
+ case 555: // option_data_data: "data" $@79 ":" "constant string"
+#line 1924 "dhcp4_parser.yy"
+ {
+ ElementPtr data(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("data", data);
+ ctx.leave();
+}
+#line 3166 "dhcp4_parser.cc"
+ break;
+
+ case 558: // option_data_csv_format: "csv-format" ":" "boolean"
+#line 1934 "dhcp4_parser.yy"
+ {
+ ctx.unique("csv-format", ctx.loc2pos(yystack_[2].location));
+ ElementPtr space(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("csv-format", space);
+}
+#line 3176 "dhcp4_parser.cc"
+ break;
+
+ case 559: // option_data_always_send: "always-send" ":" "boolean"
+#line 1940 "dhcp4_parser.yy"
+ {
+ ctx.unique("always-send", ctx.loc2pos(yystack_[2].location));
+ ElementPtr persist(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("always-send", persist);
+}
+#line 3186 "dhcp4_parser.cc"
+ break;
+
+ case 560: // $@80: %empty
+#line 1949 "dhcp4_parser.yy"
+ {
+ ctx.unique("pools", ctx.loc2pos(yystack_[0].location));
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("pools", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.POOLS);
+}
+#line 3198 "dhcp4_parser.cc"
+ break;
+
+ case 561: // pools_list: "pools" $@80 ":" "[" pools_list_content "]"
+#line 1955 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 3207 "dhcp4_parser.cc"
+ break;
+
+ case 566: // not_empty_pools_list: not_empty_pools_list ","
+#line 1968 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 3215 "dhcp4_parser.cc"
+ break;
+
+ case 567: // $@81: %empty
+#line 1973 "dhcp4_parser.yy"
+ {
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+}
+#line 3225 "dhcp4_parser.cc"
+ break;
+
+ case 568: // pool_list_entry: "{" $@81 pool_params "}"
+#line 1977 "dhcp4_parser.yy"
+ {
+ // The pool parameter is required.
+ ctx.require("pool", ctx.loc2pos(yystack_[3].location), ctx.loc2pos(yystack_[0].location));
+ ctx.stack_.pop_back();
+}
+#line 3235 "dhcp4_parser.cc"
+ break;
+
+ case 569: // $@82: %empty
+#line 1983 "dhcp4_parser.yy"
+ {
+ // Parse the pool list entry map
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(m);
+}
+#line 3245 "dhcp4_parser.cc"
+ break;
+
+ case 570: // sub_pool4: "{" $@82 pool_params "}"
+#line 1987 "dhcp4_parser.yy"
+ {
+ // The pool parameter is required.
+ ctx.require("pool", ctx.loc2pos(yystack_[3].location), ctx.loc2pos(yystack_[0].location));
+ // parsing completed
+}
+#line 3255 "dhcp4_parser.cc"
+ break;
+
+ case 573: // pool_params: pool_params ","
+#line 1995 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 3263 "dhcp4_parser.cc"
+ break;
+
+ case 581: // $@83: %empty
+#line 2009 "dhcp4_parser.yy"
+ {
+ ctx.unique("pool", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3272 "dhcp4_parser.cc"
+ break;
+
+ case 582: // pool_entry: "pool" $@83 ":" "constant string"
+#line 2012 "dhcp4_parser.yy"
+ {
+ ElementPtr pool(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("pool", pool);
+ ctx.leave();
+}
+#line 3282 "dhcp4_parser.cc"
+ break;
+
+ case 583: // $@84: %empty
+#line 2018 "dhcp4_parser.yy"
+ {
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3290 "dhcp4_parser.cc"
+ break;
+
+ case 584: // user_context: "user-context" $@84 ":" map_value
+#line 2020 "dhcp4_parser.yy"
+ {
+ ElementPtr parent = ctx.stack_.back();
+ ElementPtr user_context = yystack_[0].value.as < ElementPtr > ();
+ ConstElementPtr old = parent->get("user-context");
+
+ // Handle already existing user context
+ if (old) {
+ // Check if it was a comment or a duplicate
+ if ((old->size() != 1) || !old->contains("comment")) {
+ std::stringstream msg;
+ msg << "duplicate user-context entries (previous at "
+ << old->getPosition().str() << ")";
+ error(yystack_[3].location, msg.str());
+ }
+ // Merge the comment
+ user_context->set("comment", old->get("comment"));
+ }
+
+ // Set the user context
+ parent->set("user-context", user_context);
+ ctx.leave();
+}
+#line 3317 "dhcp4_parser.cc"
+ break;
+
+ case 585: // $@85: %empty
+#line 2043 "dhcp4_parser.yy"
+ {
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3325 "dhcp4_parser.cc"
+ break;
+
+ case 586: // comment: "comment" $@85 ":" "constant string"
+#line 2045 "dhcp4_parser.yy"
+ {
+ ElementPtr parent = ctx.stack_.back();
+ ElementPtr user_context(new MapElement(ctx.loc2pos(yystack_[3].location)));
+ ElementPtr comment(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ user_context->set("comment", comment);
+
+ // Handle already existing user context
+ ConstElementPtr old = parent->get("user-context");
+ if (old) {
+ // Check for duplicate comment
+ if (old->contains("comment")) {
+ std::stringstream msg;
+ msg << "duplicate user-context/comment entries (previous at "
+ << old->getPosition().str() << ")";
+ error(yystack_[3].location, msg.str());
+ }
+ // Merge the user context in the comment
+ merge(user_context, old);
+ }
+
+ // Set the user context
+ parent->set("user-context", user_context);
+ ctx.leave();
+}
+#line 3354 "dhcp4_parser.cc"
+ break;
+
+ case 587: // $@86: %empty
+#line 2073 "dhcp4_parser.yy"
+ {
+ ctx.unique("reservations", ctx.loc2pos(yystack_[0].location));
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("reservations", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.RESERVATIONS);
+}
+#line 3366 "dhcp4_parser.cc"
+ break;
+
+ case 588: // reservations: "reservations" $@86 ":" "[" reservations_list "]"
+#line 2079 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 3375 "dhcp4_parser.cc"
+ break;
+
+ case 593: // not_empty_reservations_list: not_empty_reservations_list ","
+#line 2090 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 3383 "dhcp4_parser.cc"
+ break;
+
+ case 594: // $@87: %empty
+#line 2095 "dhcp4_parser.yy"
+ {
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+}
+#line 3393 "dhcp4_parser.cc"
+ break;
+
+ case 595: // reservation: "{" $@87 reservation_params "}"
+#line 2099 "dhcp4_parser.yy"
+ {
+ /// @todo: an identifier parameter is required.
+ ctx.stack_.pop_back();
+}
+#line 3402 "dhcp4_parser.cc"
+ break;
+
+ case 596: // $@88: %empty
+#line 2104 "dhcp4_parser.yy"
+ {
+ // Parse the reservations list entry map
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(m);
+}
+#line 3412 "dhcp4_parser.cc"
+ break;
+
+ case 597: // sub_reservation: "{" $@88 reservation_params "}"
+#line 2108 "dhcp4_parser.yy"
+ {
+ /// @todo: an identifier parameter is required.
+ // parsing completed
+}
+#line 3421 "dhcp4_parser.cc"
+ break;
+
+ case 602: // not_empty_reservation_params: not_empty_reservation_params ","
+#line 2119 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 3429 "dhcp4_parser.cc"
+ break;
+
+ case 618: // $@89: %empty
+#line 2142 "dhcp4_parser.yy"
+ {
+ ctx.unique("next-server", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3438 "dhcp4_parser.cc"
+ break;
+
+ case 619: // next_server: "next-server" $@89 ":" "constant string"
+#line 2145 "dhcp4_parser.yy"
+ {
+ ElementPtr next_server(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("next-server", next_server);
+ ctx.leave();
+}
+#line 3448 "dhcp4_parser.cc"
+ break;
+
+ case 620: // $@90: %empty
+#line 2151 "dhcp4_parser.yy"
+ {
+ ctx.unique("server-hostname", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3457 "dhcp4_parser.cc"
+ break;
+
+ case 621: // server_hostname: "server-hostname" $@90 ":" "constant string"
+#line 2154 "dhcp4_parser.yy"
+ {
+ ElementPtr srv(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("server-hostname", srv);
+ ctx.leave();
+}
+#line 3467 "dhcp4_parser.cc"
+ break;
+
+ case 622: // $@91: %empty
+#line 2160 "dhcp4_parser.yy"
+ {
+ ctx.unique("boot-file-name", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3476 "dhcp4_parser.cc"
+ break;
+
+ case 623: // boot_file_name: "boot-file-name" $@91 ":" "constant string"
+#line 2163 "dhcp4_parser.yy"
+ {
+ ElementPtr bootfile(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("boot-file-name", bootfile);
+ ctx.leave();
+}
+#line 3486 "dhcp4_parser.cc"
+ break;
+
+ case 624: // $@92: %empty
+#line 2169 "dhcp4_parser.yy"
+ {
+ ctx.unique("ip-address", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3495 "dhcp4_parser.cc"
+ break;
+
+ case 625: // ip_address: "ip-address" $@92 ":" "constant string"
+#line 2172 "dhcp4_parser.yy"
+ {
+ ElementPtr addr(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("ip-address", addr);
+ ctx.leave();
+}
+#line 3505 "dhcp4_parser.cc"
+ break;
+
+ case 626: // $@93: %empty
+#line 2178 "dhcp4_parser.yy"
+ {
+ ctx.unique("ip-addresses", ctx.loc2pos(yystack_[0].location));
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("ip-addresses", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3517 "dhcp4_parser.cc"
+ break;
+
+ case 627: // ip_addresses: "ip-addresses" $@93 ":" list_strings
+#line 2184 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 3526 "dhcp4_parser.cc"
+ break;
+
+ case 628: // $@94: %empty
+#line 2189 "dhcp4_parser.yy"
+ {
+ ctx.unique("duid", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3535 "dhcp4_parser.cc"
+ break;
+
+ case 629: // duid: "duid" $@94 ":" "constant string"
+#line 2192 "dhcp4_parser.yy"
+ {
+ ElementPtr d(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("duid", d);
+ ctx.leave();
+}
+#line 3545 "dhcp4_parser.cc"
+ break;
+
+ case 630: // $@95: %empty
+#line 2198 "dhcp4_parser.yy"
+ {
+ ctx.unique("hw-address", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3554 "dhcp4_parser.cc"
+ break;
+
+ case 631: // hw_address: "hw-address" $@95 ":" "constant string"
+#line 2201 "dhcp4_parser.yy"
+ {
+ ElementPtr hw(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("hw-address", hw);
+ ctx.leave();
+}
+#line 3564 "dhcp4_parser.cc"
+ break;
+
+ case 632: // $@96: %empty
+#line 2207 "dhcp4_parser.yy"
+ {
+ ctx.unique("client-id", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3573 "dhcp4_parser.cc"
+ break;
+
+ case 633: // client_id_value: "client-id" $@96 ":" "constant string"
+#line 2210 "dhcp4_parser.yy"
+ {
+ ElementPtr hw(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("client-id", hw);
+ ctx.leave();
+}
+#line 3583 "dhcp4_parser.cc"
+ break;
+
+ case 634: // $@97: %empty
+#line 2216 "dhcp4_parser.yy"
+ {
+ ctx.unique("circuit-id", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3592 "dhcp4_parser.cc"
+ break;
+
+ case 635: // circuit_id_value: "circuit-id" $@97 ":" "constant string"
+#line 2219 "dhcp4_parser.yy"
+ {
+ ElementPtr hw(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("circuit-id", hw);
+ ctx.leave();
+}
+#line 3602 "dhcp4_parser.cc"
+ break;
+
+ case 636: // $@98: %empty
+#line 2225 "dhcp4_parser.yy"
+ {
+ ctx.unique("flex-id", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3611 "dhcp4_parser.cc"
+ break;
+
+ case 637: // flex_id_value: "flex-id" $@98 ":" "constant string"
+#line 2228 "dhcp4_parser.yy"
+ {
+ ElementPtr hw(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("flex-id", hw);
+ ctx.leave();
+}
+#line 3621 "dhcp4_parser.cc"
+ break;
+
+ case 638: // $@99: %empty
+#line 2234 "dhcp4_parser.yy"
+ {
+ ctx.unique("hostname", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3630 "dhcp4_parser.cc"
+ break;
+
+ case 639: // hostname: "hostname" $@99 ":" "constant string"
+#line 2237 "dhcp4_parser.yy"
+ {
+ ElementPtr host(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("hostname", host);
+ ctx.leave();
+}
+#line 3640 "dhcp4_parser.cc"
+ break;
+
+ case 640: // $@100: %empty
+#line 2243 "dhcp4_parser.yy"
+ {
+ ctx.unique("client-classes", ctx.loc2pos(yystack_[0].location));
+ ElementPtr c(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("client-classes", c);
+ ctx.stack_.push_back(c);
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3652 "dhcp4_parser.cc"
+ break;
+
+ case 641: // reservation_client_classes: "client-classes" $@100 ":" list_strings
+#line 2249 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 3661 "dhcp4_parser.cc"
+ break;
+
+ case 642: // $@101: %empty
+#line 2257 "dhcp4_parser.yy"
+ {
+ ctx.unique("relay", ctx.loc2pos(yystack_[0].location));
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("relay", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.RELAY);
+}
+#line 3673 "dhcp4_parser.cc"
+ break;
+
+ case 643: // relay: "relay" $@101 ":" "{" relay_map "}"
+#line 2263 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 3682 "dhcp4_parser.cc"
+ break;
+
+ case 646: // $@102: %empty
+#line 2275 "dhcp4_parser.yy"
+ {
+ ctx.unique("client-classes", ctx.loc2pos(yystack_[0].location));
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("client-classes", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.CLIENT_CLASSES);
+}
+#line 3694 "dhcp4_parser.cc"
+ break;
+
+ case 647: // client_classes: "client-classes" $@102 ":" "[" client_classes_list "]"
+#line 2281 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 3703 "dhcp4_parser.cc"
+ break;
+
+ case 650: // client_classes_list: client_classes_list ","
+#line 2288 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 3711 "dhcp4_parser.cc"
+ break;
+
+ case 651: // $@103: %empty
+#line 2293 "dhcp4_parser.yy"
+ {
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+}
+#line 3721 "dhcp4_parser.cc"
+ break;
+
+ case 652: // client_class_entry: "{" $@103 client_class_params "}"
+#line 2297 "dhcp4_parser.yy"
+ {
+ // The name client class parameter is required.
+ ctx.require("name", ctx.loc2pos(yystack_[3].location), ctx.loc2pos(yystack_[0].location));
+ ctx.stack_.pop_back();
+}
+#line 3731 "dhcp4_parser.cc"
+ break;
+
+ case 657: // not_empty_client_class_params: not_empty_client_class_params ","
+#line 2309 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 3739 "dhcp4_parser.cc"
+ break;
+
+ case 673: // $@104: %empty
+#line 2332 "dhcp4_parser.yy"
+ {
+ ctx.unique("test", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3748 "dhcp4_parser.cc"
+ break;
+
+ case 674: // client_class_test: "test" $@104 ":" "constant string"
+#line 2335 "dhcp4_parser.yy"
+ {
+ ElementPtr test(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("test", test);
+ ctx.leave();
+}
+#line 3758 "dhcp4_parser.cc"
+ break;
+
+ case 675: // only_if_required: "only-if-required" ":" "boolean"
+#line 2341 "dhcp4_parser.yy"
+ {
+ ctx.unique("only-if-required", ctx.loc2pos(yystack_[2].location));
+ ElementPtr b(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("only-if-required", b);
+}
+#line 3768 "dhcp4_parser.cc"
+ break;
+
+ case 676: // dhcp4o6_port: "dhcp4o6-port" ":" "integer"
+#line 2349 "dhcp4_parser.yy"
+ {
+ ctx.unique("dhcp4o6-port", ctx.loc2pos(yystack_[2].location));
+ ElementPtr time(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("dhcp4o6-port", time);
+}
+#line 3778 "dhcp4_parser.cc"
+ break;
+
+ case 677: // $@105: %empty
+#line 2357 "dhcp4_parser.yy"
+ {
+ ctx.unique("control-socket", ctx.loc2pos(yystack_[0].location));
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("control-socket", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.CONTROL_SOCKET);
+}
+#line 3790 "dhcp4_parser.cc"
+ break;
+
+ case 678: // control_socket: "control-socket" $@105 ":" "{" control_socket_params "}"
+#line 2363 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 3799 "dhcp4_parser.cc"
+ break;
+
+ case 681: // control_socket_params: control_socket_params ","
+#line 2370 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 3807 "dhcp4_parser.cc"
+ break;
+
+ case 687: // $@106: %empty
+#line 2382 "dhcp4_parser.yy"
+ {
+ ctx.unique("socket-type", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3816 "dhcp4_parser.cc"
+ break;
+
+ case 688: // control_socket_type: "socket-type" $@106 ":" "constant string"
+#line 2385 "dhcp4_parser.yy"
+ {
+ ElementPtr stype(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("socket-type", stype);
+ ctx.leave();
+}
+#line 3826 "dhcp4_parser.cc"
+ break;
+
+ case 689: // $@107: %empty
+#line 2391 "dhcp4_parser.yy"
+ {
+ ctx.unique("socket-name", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3835 "dhcp4_parser.cc"
+ break;
+
+ case 690: // control_socket_name: "socket-name" $@107 ":" "constant string"
+#line 2394 "dhcp4_parser.yy"
+ {
+ ElementPtr name(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("socket-name", name);
+ ctx.leave();
+}
+#line 3845 "dhcp4_parser.cc"
+ break;
+
+ case 691: // $@108: %empty
+#line 2403 "dhcp4_parser.yy"
+ {
+ ctx.unique("dhcp-queue-control", ctx.loc2pos(yystack_[0].location));
+ ElementPtr qc(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("dhcp-queue-control", qc);
+ ctx.stack_.push_back(qc);
+ ctx.enter(ctx.DHCP_QUEUE_CONTROL);
+}
+#line 3857 "dhcp4_parser.cc"
+ break;
+
+ case 692: // dhcp_queue_control: "dhcp-queue-control" $@108 ":" "{" queue_control_params "}"
+#line 2409 "dhcp4_parser.yy"
+ {
+ // The enable queue parameter is required.
+ ctx.require("enable-queue", ctx.loc2pos(yystack_[2].location), ctx.loc2pos(yystack_[0].location));
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 3868 "dhcp4_parser.cc"
+ break;
+
+ case 695: // queue_control_params: queue_control_params ","
+#line 2418 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 3876 "dhcp4_parser.cc"
+ break;
+
+ case 702: // enable_queue: "enable-queue" ":" "boolean"
+#line 2431 "dhcp4_parser.yy"
+ {
+ ctx.unique("enable-queue", ctx.loc2pos(yystack_[2].location));
+ ElementPtr b(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("enable-queue", b);
+}
+#line 3886 "dhcp4_parser.cc"
+ break;
+
+ case 703: // $@109: %empty
+#line 2437 "dhcp4_parser.yy"
+ {
+ ctx.unique("queue-type", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3895 "dhcp4_parser.cc"
+ break;
+
+ case 704: // queue_type: "queue-type" $@109 ":" "constant string"
+#line 2440 "dhcp4_parser.yy"
+ {
+ ElementPtr qt(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("queue-type", qt);
+ ctx.leave();
+}
+#line 3905 "dhcp4_parser.cc"
+ break;
+
+ case 705: // capacity: "capacity" ":" "integer"
+#line 2446 "dhcp4_parser.yy"
+ {
+ ctx.unique("capacity", ctx.loc2pos(yystack_[2].location));
+ ElementPtr c(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("capacity", c);
+}
+#line 3915 "dhcp4_parser.cc"
+ break;
+
+ case 706: // $@110: %empty
+#line 2452 "dhcp4_parser.yy"
+ {
+ ctx.unique(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 3924 "dhcp4_parser.cc"
+ break;
+
+ case 707: // arbitrary_map_entry: "constant string" $@110 ":" value
+#line 2455 "dhcp4_parser.yy"
+ {
+ ctx.stack_.back()->set(yystack_[3].value.as < std::string > (), yystack_[0].value.as < ElementPtr > ());
+ ctx.leave();
+}
+#line 3933 "dhcp4_parser.cc"
+ break;
+
+ case 708: // $@111: %empty
+#line 2462 "dhcp4_parser.yy"
+ {
+ ctx.unique("dhcp-ddns", ctx.loc2pos(yystack_[0].location));
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("dhcp-ddns", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.DHCP_DDNS);
+}
+#line 3945 "dhcp4_parser.cc"
+ break;
+
+ case 709: // dhcp_ddns: "dhcp-ddns" $@111 ":" "{" dhcp_ddns_params "}"
+#line 2468 "dhcp4_parser.yy"
+ {
+ // The enable updates DHCP DDNS parameter is required.
+ ctx.require("enable-updates", ctx.loc2pos(yystack_[2].location), ctx.loc2pos(yystack_[0].location));
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 3956 "dhcp4_parser.cc"
+ break;
+
+ case 710: // $@112: %empty
+#line 2475 "dhcp4_parser.yy"
+ {
+ // Parse the dhcp-ddns map
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(m);
+}
+#line 3966 "dhcp4_parser.cc"
+ break;
+
+ case 711: // sub_dhcp_ddns: "{" $@112 dhcp_ddns_params "}"
+#line 2479 "dhcp4_parser.yy"
+ {
+ // The enable updates DHCP DDNS parameter is required.
+ ctx.require("enable-updates", ctx.loc2pos(yystack_[3].location), ctx.loc2pos(yystack_[0].location));
+ // parsing completed
+}
+#line 3976 "dhcp4_parser.cc"
+ break;
+
+ case 714: // dhcp_ddns_params: dhcp_ddns_params ","
+#line 2487 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 3984 "dhcp4_parser.cc"
+ break;
+
+ case 733: // enable_updates: "enable-updates" ":" "boolean"
+#line 2512 "dhcp4_parser.yy"
+ {
+ ctx.unique("enable-updates", ctx.loc2pos(yystack_[2].location));
+ ElementPtr b(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("enable-updates", b);
+}
+#line 3994 "dhcp4_parser.cc"
+ break;
+
+ case 734: // $@113: %empty
+#line 2518 "dhcp4_parser.yy"
+ {
+ ctx.unique("server-ip", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 4003 "dhcp4_parser.cc"
+ break;
+
+ case 735: // server_ip: "server-ip" $@113 ":" "constant string"
+#line 2521 "dhcp4_parser.yy"
+ {
+ ElementPtr s(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("server-ip", s);
+ ctx.leave();
+}
+#line 4013 "dhcp4_parser.cc"
+ break;
+
+ case 736: // server_port: "server-port" ":" "integer"
+#line 2527 "dhcp4_parser.yy"
+ {
+ ctx.unique("server-port", ctx.loc2pos(yystack_[2].location));
+ ElementPtr i(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("server-port", i);
+}
+#line 4023 "dhcp4_parser.cc"
+ break;
+
+ case 737: // $@114: %empty
+#line 2533 "dhcp4_parser.yy"
+ {
+ ctx.unique("sender-ip", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 4032 "dhcp4_parser.cc"
+ break;
+
+ case 738: // sender_ip: "sender-ip" $@114 ":" "constant string"
+#line 2536 "dhcp4_parser.yy"
+ {
+ ElementPtr s(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("sender-ip", s);
+ ctx.leave();
+}
+#line 4042 "dhcp4_parser.cc"
+ break;
+
+ case 739: // sender_port: "sender-port" ":" "integer"
+#line 2542 "dhcp4_parser.yy"
+ {
+ ctx.unique("sender-port", ctx.loc2pos(yystack_[2].location));
+ ElementPtr i(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("sender-port", i);
+}
+#line 4052 "dhcp4_parser.cc"
+ break;
+
+ case 740: // max_queue_size: "max-queue-size" ":" "integer"
+#line 2548 "dhcp4_parser.yy"
+ {
+ ctx.unique("max-queue-size", ctx.loc2pos(yystack_[2].location));
+ ElementPtr i(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("max-queue-size", i);
+}
+#line 4062 "dhcp4_parser.cc"
+ break;
+
+ case 741: // $@115: %empty
+#line 2554 "dhcp4_parser.yy"
+ {
+ ctx.unique("ncr-protocol", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NCR_PROTOCOL);
+}
+#line 4071 "dhcp4_parser.cc"
+ break;
+
+ case 742: // ncr_protocol: "ncr-protocol" $@115 ":" ncr_protocol_value
+#line 2557 "dhcp4_parser.yy"
+ {
+ ctx.stack_.back()->set("ncr-protocol", yystack_[0].value.as < ElementPtr > ());
+ ctx.leave();
+}
+#line 4080 "dhcp4_parser.cc"
+ break;
+
+ case 743: // ncr_protocol_value: "udp"
+#line 2563 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("UDP", ctx.loc2pos(yystack_[0].location))); }
+#line 4086 "dhcp4_parser.cc"
+ break;
+
+ case 744: // ncr_protocol_value: "tcp"
+#line 2564 "dhcp4_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("TCP", ctx.loc2pos(yystack_[0].location))); }
+#line 4092 "dhcp4_parser.cc"
+ break;
+
+ case 745: // $@116: %empty
+#line 2567 "dhcp4_parser.yy"
+ {
+ ctx.unique("ncr-format", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NCR_FORMAT);
+}
+#line 4101 "dhcp4_parser.cc"
+ break;
+
+ case 746: // ncr_format: "ncr-format" $@116 ":" "JSON"
+#line 2570 "dhcp4_parser.yy"
+ {
+ ElementPtr json(new StringElement("JSON", ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("ncr-format", json);
+ ctx.leave();
+}
+#line 4111 "dhcp4_parser.cc"
+ break;
+
+ case 747: // $@117: %empty
+#line 2577 "dhcp4_parser.yy"
+ {
+ ctx.unique("qualifying-suffix", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 4120 "dhcp4_parser.cc"
+ break;
+
+ case 748: // dep_qualifying_suffix: "qualifying-suffix" $@117 ":" "constant string"
+#line 2580 "dhcp4_parser.yy"
+ {
+ ElementPtr s(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("qualifying-suffix", s);
+ ctx.leave();
+}
+#line 4130 "dhcp4_parser.cc"
+ break;
+
+ case 749: // dep_override_no_update: "override-no-update" ":" "boolean"
+#line 2587 "dhcp4_parser.yy"
+ {
+ ctx.unique("override-no-update", ctx.loc2pos(yystack_[2].location));
+ ElementPtr b(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("override-no-update", b);
+}
+#line 4140 "dhcp4_parser.cc"
+ break;
+
+ case 750: // dep_override_client_update: "override-client-update" ":" "boolean"
+#line 2594 "dhcp4_parser.yy"
+ {
+ ctx.unique("override-client-update", ctx.loc2pos(yystack_[2].location));
+ ElementPtr b(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("override-client-update", b);
+}
+#line 4150 "dhcp4_parser.cc"
+ break;
+
+ case 751: // $@118: %empty
+#line 2601 "dhcp4_parser.yy"
+ {
+ ctx.unique("replace-client-name", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.REPLACE_CLIENT_NAME);
+}
+#line 4159 "dhcp4_parser.cc"
+ break;
+
+ case 752: // dep_replace_client_name: "replace-client-name" $@118 ":" ddns_replace_client_name_value
+#line 2604 "dhcp4_parser.yy"
+ {
+ ctx.stack_.back()->set("replace-client-name", yystack_[0].value.as < ElementPtr > ());
+ ctx.leave();
+}
+#line 4168 "dhcp4_parser.cc"
+ break;
+
+ case 753: // $@119: %empty
+#line 2610 "dhcp4_parser.yy"
+ {
+ ctx.unique("generated-prefix", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 4177 "dhcp4_parser.cc"
+ break;
+
+ case 754: // dep_generated_prefix: "generated-prefix" $@119 ":" "constant string"
+#line 2613 "dhcp4_parser.yy"
+ {
+ ElementPtr s(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("generated-prefix", s);
+ ctx.leave();
+}
+#line 4187 "dhcp4_parser.cc"
+ break;
+
+ case 755: // $@120: %empty
+#line 2620 "dhcp4_parser.yy"
+ {
+ ctx.unique("hostname-char-set", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 4196 "dhcp4_parser.cc"
+ break;
+
+ case 756: // dep_hostname_char_set: "hostname-char-set" $@120 ":" "constant string"
+#line 2623 "dhcp4_parser.yy"
+ {
+ ElementPtr s(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("hostname-char-set", s);
+ ctx.leave();
+}
+#line 4206 "dhcp4_parser.cc"
+ break;
+
+ case 757: // $@121: %empty
+#line 2630 "dhcp4_parser.yy"
+ {
+ ctx.unique("hostname-char-replacement", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 4215 "dhcp4_parser.cc"
+ break;
+
+ case 758: // dep_hostname_char_replacement: "hostname-char-replacement" $@121 ":" "constant string"
+#line 2633 "dhcp4_parser.yy"
+ {
+ ElementPtr s(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("hostname-char-replacement", s);
+ ctx.leave();
+}
+#line 4225 "dhcp4_parser.cc"
+ break;
+
+ case 759: // $@122: %empty
+#line 2642 "dhcp4_parser.yy"
+ {
+ ctx.unique("config-control", ctx.loc2pos(yystack_[0].location));
+ ElementPtr i(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("config-control", i);
+ ctx.stack_.push_back(i);
+ ctx.enter(ctx.CONFIG_CONTROL);
+}
+#line 4237 "dhcp4_parser.cc"
+ break;
+
+ case 760: // config_control: "config-control" $@122 ":" "{" config_control_params "}"
+#line 2648 "dhcp4_parser.yy"
+ {
+ // No config control params are required
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 4247 "dhcp4_parser.cc"
+ break;
+
+ case 761: // $@123: %empty
+#line 2654 "dhcp4_parser.yy"
+ {
+ // Parse the config-control map
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(m);
+}
+#line 4257 "dhcp4_parser.cc"
+ break;
+
+ case 762: // sub_config_control: "{" $@123 config_control_params "}"
+#line 2658 "dhcp4_parser.yy"
+ {
+ // No config_control params are required
+ // parsing completed
+}
+#line 4266 "dhcp4_parser.cc"
+ break;
+
+ case 765: // config_control_params: config_control_params ","
+#line 2666 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 4274 "dhcp4_parser.cc"
+ break;
+
+ case 768: // $@124: %empty
+#line 2676 "dhcp4_parser.yy"
+ {
+ ctx.unique("config-databases", ctx.loc2pos(yystack_[0].location));
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("config-databases", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.CONFIG_DATABASE);
+}
+#line 4286 "dhcp4_parser.cc"
+ break;
+
+ case 769: // config_databases: "config-databases" $@124 ":" "[" database_list "]"
+#line 2682 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 4295 "dhcp4_parser.cc"
+ break;
+
+ case 770: // config_fetch_wait_time: "config-fetch-wait-time" ":" "integer"
+#line 2687 "dhcp4_parser.yy"
+ {
+ ctx.unique("config-fetch-wait-time", ctx.loc2pos(yystack_[2].location));
+ ElementPtr value(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("config-fetch-wait-time", value);
+}
+#line 4305 "dhcp4_parser.cc"
+ break;
+
+ case 771: // $@125: %empty
+#line 2695 "dhcp4_parser.yy"
+ {
+ ctx.unique("loggers", ctx.loc2pos(yystack_[0].location));
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("loggers", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.LOGGERS);
+}
+#line 4317 "dhcp4_parser.cc"
+ break;
+
+ case 772: // loggers: "loggers" $@125 ":" "[" loggers_entries "]"
+#line 2701 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 4326 "dhcp4_parser.cc"
+ break;
+
+ case 775: // loggers_entries: loggers_entries ","
+#line 2710 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 4334 "dhcp4_parser.cc"
+ break;
+
+ case 776: // $@126: %empty
+#line 2716 "dhcp4_parser.yy"
+ {
+ ElementPtr l(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(l);
+ ctx.stack_.push_back(l);
+}
+#line 4344 "dhcp4_parser.cc"
+ break;
+
+ case 777: // logger_entry: "{" $@126 logger_params "}"
+#line 2720 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+}
+#line 4352 "dhcp4_parser.cc"
+ break;
+
+ case 780: // logger_params: logger_params ","
+#line 2726 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 4360 "dhcp4_parser.cc"
+ break;
+
+ case 788: // debuglevel: "debuglevel" ":" "integer"
+#line 2740 "dhcp4_parser.yy"
+ {
+ ctx.unique("debuglevel", ctx.loc2pos(yystack_[2].location));
+ ElementPtr dl(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("debuglevel", dl);
+}
+#line 4370 "dhcp4_parser.cc"
+ break;
+
+ case 789: // $@127: %empty
+#line 2746 "dhcp4_parser.yy"
+ {
+ ctx.unique("severity", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 4379 "dhcp4_parser.cc"
+ break;
+
+ case 790: // severity: "severity" $@127 ":" "constant string"
+#line 2749 "dhcp4_parser.yy"
+ {
+ ElementPtr sev(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("severity", sev);
+ ctx.leave();
+}
+#line 4389 "dhcp4_parser.cc"
+ break;
+
+ case 791: // $@128: %empty
+#line 2755 "dhcp4_parser.yy"
+ {
+ ctx.unique("output_options", ctx.loc2pos(yystack_[0].location));
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("output_options", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.OUTPUT_OPTIONS);
+}
+#line 4401 "dhcp4_parser.cc"
+ break;
+
+ case 792: // output_options_list: "output_options" $@128 ":" "[" output_options_list_content "]"
+#line 2761 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 4410 "dhcp4_parser.cc"
+ break;
+
+ case 795: // output_options_list_content: output_options_list_content ","
+#line 2768 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 4418 "dhcp4_parser.cc"
+ break;
+
+ case 796: // $@129: %empty
+#line 2773 "dhcp4_parser.yy"
+ {
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+}
+#line 4428 "dhcp4_parser.cc"
+ break;
+
+ case 797: // output_entry: "{" $@129 output_params_list "}"
+#line 2777 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+}
+#line 4436 "dhcp4_parser.cc"
+ break;
+
+ case 800: // output_params_list: output_params_list ","
+#line 2783 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 4444 "dhcp4_parser.cc"
+ break;
+
+ case 806: // $@130: %empty
+#line 2795 "dhcp4_parser.yy"
+ {
+ ctx.unique("output", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 4453 "dhcp4_parser.cc"
+ break;
+
+ case 807: // output: "output" $@130 ":" "constant string"
+#line 2798 "dhcp4_parser.yy"
+ {
+ ElementPtr sev(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("output", sev);
+ ctx.leave();
+}
+#line 4463 "dhcp4_parser.cc"
+ break;
+
+ case 808: // flush: "flush" ":" "boolean"
+#line 2804 "dhcp4_parser.yy"
+ {
+ ctx.unique("flush", ctx.loc2pos(yystack_[2].location));
+ ElementPtr flush(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("flush", flush);
+}
+#line 4473 "dhcp4_parser.cc"
+ break;
+
+ case 809: // maxsize: "maxsize" ":" "integer"
+#line 2810 "dhcp4_parser.yy"
+ {
+ ctx.unique("maxsize", ctx.loc2pos(yystack_[2].location));
+ ElementPtr maxsize(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("maxsize", maxsize);
+}
+#line 4483 "dhcp4_parser.cc"
+ break;
+
+ case 810: // maxver: "maxver" ":" "integer"
+#line 2816 "dhcp4_parser.yy"
+ {
+ ctx.unique("maxver", ctx.loc2pos(yystack_[2].location));
+ ElementPtr maxver(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("maxver", maxver);
+}
+#line 4493 "dhcp4_parser.cc"
+ break;
+
+ case 811: // $@131: %empty
+#line 2822 "dhcp4_parser.yy"
+ {
+ ctx.unique("pattern", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 4502 "dhcp4_parser.cc"
+ break;
+
+ case 812: // pattern: "pattern" $@131 ":" "constant string"
+#line 2825 "dhcp4_parser.yy"
+ {
+ ElementPtr sev(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("pattern", sev);
+ ctx.leave();
+}
+#line 4512 "dhcp4_parser.cc"
+ break;
+
+ case 813: // $@132: %empty
+#line 2831 "dhcp4_parser.yy"
+ {
+ ctx.unique("compatibility", ctx.loc2pos(yystack_[0].location));
+ ElementPtr i(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("compatibility", i);
+ ctx.stack_.push_back(i);
+ ctx.enter(ctx.COMPATIBILITY);
+}
+#line 4524 "dhcp4_parser.cc"
+ break;
+
+ case 814: // compatibility: "compatibility" $@132 ":" "{" compatibility_params "}"
+#line 2837 "dhcp4_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 4533 "dhcp4_parser.cc"
+ break;
+
+ case 817: // compatibility_params: compatibility_params ","
+#line 2844 "dhcp4_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 4541 "dhcp4_parser.cc"
+ break;
+
+ case 820: // lenient_option_parsing: "lenient-option-parsing" ":" "boolean"
+#line 2853 "dhcp4_parser.yy"
+ {
+ ctx.unique("lenient-option-parsing", ctx.loc2pos(yystack_[2].location));
+ ElementPtr b(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("lenient-option-parsing", b);
+}
+#line 4551 "dhcp4_parser.cc"
+ break;
+
+
+#line 4555 "dhcp4_parser.cc"
+
+ default:
+ break;
+ }
+ }
+#if YY_EXCEPTIONS
+ catch (const syntax_error& yyexc)
+ {
+ YYCDEBUG << "Caught exception: " << yyexc.what() << '\n';
+ error (yyexc);
+ YYERROR;
+ }
+#endif // YY_EXCEPTIONS
+ YY_SYMBOL_PRINT ("-> $$ =", yylhs);
+ yypop_ (yylen);
+ yylen = 0;
+
+ // Shift the result of the reduction.
+ yypush_ (YY_NULLPTR, YY_MOVE (yylhs));
+ }
+ goto yynewstate;
+
+
+ /*--------------------------------------.
+ | yyerrlab -- here on detecting error. |
+ `--------------------------------------*/
+ yyerrlab:
+ // If not already recovering from an error, report this error.
+ if (!yyerrstatus_)
+ {
+ ++yynerrs_;
+ context yyctx (*this, yyla);
+ std::string msg = yysyntax_error_ (yyctx);
+ error (yyla.location, YY_MOVE (msg));
+ }
+
+
+ yyerror_range[1].location = yyla.location;
+ if (yyerrstatus_ == 3)
+ {
+ /* If just tried and failed to reuse lookahead token after an
+ error, discard it. */
+
+ // Return failure if at end of input.
+ if (yyla.kind () == symbol_kind::S_YYEOF)
+ YYABORT;
+ else if (!yyla.empty ())
+ {
+ yy_destroy_ ("Error: discarding", yyla);
+ yyla.clear ();
+ }
+ }
+
+ // Else will try to reuse lookahead token after shifting the error token.
+ goto yyerrlab1;
+
+
+ /*---------------------------------------------------.
+ | yyerrorlab -- error raised explicitly by YYERROR. |
+ `---------------------------------------------------*/
+ yyerrorlab:
+ /* Pacify compilers when the user code never invokes YYERROR and
+ the label yyerrorlab therefore never appears in user code. */
+ if (false)
+ YYERROR;
+
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYERROR. */
+ yypop_ (yylen);
+ yylen = 0;
+ YY_STACK_PRINT ();
+ goto yyerrlab1;
+
+
+ /*-------------------------------------------------------------.
+ | yyerrlab1 -- common code for both syntax error and YYERROR. |
+ `-------------------------------------------------------------*/
+ yyerrlab1:
+ yyerrstatus_ = 3; // Each real token shifted decrements this.
+ // Pop stack until we find a state that shifts the error token.
+ for (;;)
+ {
+ yyn = yypact_[+yystack_[0].state];
+ if (!yy_pact_value_is_default_ (yyn))
+ {
+ yyn += symbol_kind::S_YYerror;
+ if (0 <= yyn && yyn <= yylast_
+ && yycheck_[yyn] == symbol_kind::S_YYerror)
+ {
+ yyn = yytable_[yyn];
+ if (0 < yyn)
+ break;
+ }
+ }
+
+ // Pop the current state because it cannot handle the error token.
+ if (yystack_.size () == 1)
+ YYABORT;
+
+ yyerror_range[1].location = yystack_[0].location;
+ yy_destroy_ ("Error: popping", yystack_[0]);
+ yypop_ ();
+ YY_STACK_PRINT ();
+ }
+ {
+ stack_symbol_type error_token;
+
+ yyerror_range[2].location = yyla.location;
+ YYLLOC_DEFAULT (error_token.location, yyerror_range, 2);
+
+ // Shift the error token.
+ error_token.state = state_type (yyn);
+ yypush_ ("Shifting", YY_MOVE (error_token));
+ }
+ goto yynewstate;
+
+
+ /*-------------------------------------.
+ | yyacceptlab -- YYACCEPT comes here. |
+ `-------------------------------------*/
+ yyacceptlab:
+ yyresult = 0;
+ goto yyreturn;
+
+
+ /*-----------------------------------.
+ | yyabortlab -- YYABORT comes here. |
+ `-----------------------------------*/
+ yyabortlab:
+ yyresult = 1;
+ goto yyreturn;
+
+
+ /*-----------------------------------------------------.
+ | yyreturn -- parsing is finished, return the result. |
+ `-----------------------------------------------------*/
+ yyreturn:
+ if (!yyla.empty ())
+ yy_destroy_ ("Cleanup: discarding lookahead", yyla);
+
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYABORT or YYACCEPT. */
+ yypop_ (yylen);
+ YY_STACK_PRINT ();
+ while (1 < yystack_.size ())
+ {
+ yy_destroy_ ("Cleanup: popping", yystack_[0]);
+ yypop_ ();
+ }
+
+ return yyresult;
+ }
+#if YY_EXCEPTIONS
+ catch (...)
+ {
+ YYCDEBUG << "Exception caught: cleaning lookahead and stack\n";
+ // Do not try to display the values of the reclaimed symbols,
+ // as their printers might throw an exception.
+ if (!yyla.empty ())
+ yy_destroy_ (YY_NULLPTR, yyla);
+
+ while (1 < yystack_.size ())
+ {
+ yy_destroy_ (YY_NULLPTR, yystack_[0]);
+ yypop_ ();
+ }
+ throw;
+ }
+#endif // YY_EXCEPTIONS
+ }
+
+ void
+ Dhcp4Parser::error (const syntax_error& yyexc)
+ {
+ error (yyexc.location, yyexc.what ());
+ }
+
+ /* Return YYSTR after stripping away unnecessary quotes and
+ backslashes, so that it's suitable for yyerror. The heuristic is
+ that double-quoting is unnecessary unless the string contains an
+ apostrophe, a comma, or backslash (other than backslash-backslash).
+ YYSTR is taken from yytname. */
+ std::string
+ Dhcp4Parser::yytnamerr_ (const char *yystr)
+ {
+ if (*yystr == '"')
+ {
+ std::string yyr;
+ char const *yyp = yystr;
+
+ for (;;)
+ switch (*++yyp)
+ {
+ case '\'':
+ case ',':
+ goto do_not_strip_quotes;
+
+ case '\\':
+ if (*++yyp != '\\')
+ goto do_not_strip_quotes;
+ else
+ goto append;
+
+ append:
+ default:
+ yyr += *yyp;
+ break;
+
+ case '"':
+ return yyr;
+ }
+ do_not_strip_quotes: ;
+ }
+
+ return yystr;
+ }
+
+ std::string
+ Dhcp4Parser::symbol_name (symbol_kind_type yysymbol)
+ {
+ return yytnamerr_ (yytname_[yysymbol]);
+ }
+
+
+
+ // Dhcp4Parser::context.
+ Dhcp4Parser::context::context (const Dhcp4Parser& yyparser, const symbol_type& yyla)
+ : yyparser_ (yyparser)
+ , yyla_ (yyla)
+ {}
+
+ int
+ Dhcp4Parser::context::expected_tokens (symbol_kind_type yyarg[], int yyargn) const
+ {
+ // Actual number of expected tokens
+ int yycount = 0;
+
+ const int yyn = yypact_[+yyparser_.yystack_[0].state];
+ if (!yy_pact_value_is_default_ (yyn))
+ {
+ /* Start YYX at -YYN if negative to avoid negative indexes in
+ YYCHECK. In other words, skip the first -YYN actions for
+ this state because they are default actions. */
+ const int yyxbegin = yyn < 0 ? -yyn : 0;
+ // Stay within bounds of both yycheck and yytname.
+ const int yychecklim = yylast_ - yyn + 1;
+ const int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS;
+ for (int yyx = yyxbegin; yyx < yyxend; ++yyx)
+ if (yycheck_[yyx + yyn] == yyx && yyx != symbol_kind::S_YYerror
+ && !yy_table_value_is_error_ (yytable_[yyx + yyn]))
+ {
+ if (!yyarg)
+ ++yycount;
+ else if (yycount == yyargn)
+ return 0;
+ else
+ yyarg[yycount++] = YY_CAST (symbol_kind_type, yyx);
+ }
+ }
+
+ if (yyarg && yycount == 0 && 0 < yyargn)
+ yyarg[0] = symbol_kind::S_YYEMPTY;
+ return yycount;
+ }
+
+
+
+
+
+
+ int
+ Dhcp4Parser::yy_syntax_error_arguments_ (const context& yyctx,
+ symbol_kind_type yyarg[], int yyargn) const
+ {
+ /* There are many possibilities here to consider:
+ - If this state is a consistent state with a default action, then
+ the only way this function was invoked is if the default action
+ is an error action. In that case, don't check for expected
+ tokens because there are none.
+ - The only way there can be no lookahead present (in yyla) is
+ if this state is a consistent state with a default action.
+ Thus, detecting the absence of a lookahead is sufficient to
+ determine that there is no unexpected or expected token to
+ report. In that case, just report a simple "syntax error".
+ - Don't assume there isn't a lookahead just because this state is
+ a consistent state with a default action. There might have
+ been a previous inconsistent state, consistent state with a
+ non-default action, or user semantic action that manipulated
+ yyla. (However, yyla is currently not documented for users.)
+ - Of course, the expected token list depends on states to have
+ correct lookahead information, and it depends on the parser not
+ to perform extra reductions after fetching a lookahead from the
+ scanner and before detecting a syntax error. Thus, state merging
+ (from LALR or IELR) and default reductions corrupt the expected
+ token list. However, the list is correct for canonical LR with
+ one exception: it will still contain any token that will not be
+ accepted due to an error action in a later state.
+ */
+
+ if (!yyctx.lookahead ().empty ())
+ {
+ if (yyarg)
+ yyarg[0] = yyctx.token ();
+ int yyn = yyctx.expected_tokens (yyarg ? yyarg + 1 : yyarg, yyargn - 1);
+ return yyn + 1;
+ }
+ return 0;
+ }
+
+ // Generate an error message.
+ std::string
+ Dhcp4Parser::yysyntax_error_ (const context& yyctx) const
+ {
+ // Its maximum.
+ enum { YYARGS_MAX = 5 };
+ // Arguments of yyformat.
+ symbol_kind_type yyarg[YYARGS_MAX];
+ int yycount = yy_syntax_error_arguments_ (yyctx, yyarg, YYARGS_MAX);
+
+ char const* yyformat = YY_NULLPTR;
+ switch (yycount)
+ {
+#define YYCASE_(N, S) \
+ case N: \
+ yyformat = S; \
+ break
+ default: // Avoid compiler warnings.
+ YYCASE_ (0, YY_("syntax error"));
+ YYCASE_ (1, YY_("syntax error, unexpected %s"));
+ YYCASE_ (2, YY_("syntax error, unexpected %s, expecting %s"));
+ YYCASE_ (3, YY_("syntax error, unexpected %s, expecting %s or %s"));
+ YYCASE_ (4, YY_("syntax error, unexpected %s, expecting %s or %s or %s"));
+ YYCASE_ (5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"));
+#undef YYCASE_
+ }
+
+ std::string yyres;
+ // Argument number.
+ std::ptrdiff_t yyi = 0;
+ for (char const* yyp = yyformat; *yyp; ++yyp)
+ if (yyp[0] == '%' && yyp[1] == 's' && yyi < yycount)
+ {
+ yyres += symbol_name (yyarg[yyi++]);
+ ++yyp;
+ }
+ else
+ yyres += *yyp;
+ return yyres;
+ }
+
+
+ const short Dhcp4Parser::yypact_ninf_ = -971;
+
+ const signed char Dhcp4Parser::yytable_ninf_ = -1;
+
+ const short
+ Dhcp4Parser::yypact_[] =
+ {
+ 429, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, 39, 38, 26, 48, 62, 74,
+ 92, 96, 106, 114, 118, 140, 148, 163, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, 38, -152, 125, 132, 126, 294,
+ -50, 315, -16, 80, 200, -84, 617, 77, -971, 181,
+ 154, 228, 209, 258, -971, 34, -971, -971, -971, -971,
+ 264, 266, 275, -971, -971, -971, -971, -971, -971, 276,
+ 300, 325, 332, 348, 360, 361, 373, 377, 383, 384,
+ -971, 385, 386, 387, 388, 389, -971, -971, -971, 391,
+ 400, 401, -971, -971, -971, 405, -971, -971, -971, -971,
+ 406, 408, 409, -971, -971, -971, -971, -971, 412, -971,
+ -971, -971, -971, -971, -971, 415, 416, 417, -971, -971,
+ 419, -971, 56, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ 421, 422, 423, 424, -971, 79, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, 425, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ 85, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, 93, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, 343, 427, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, 426,
+ -971, -971, 428, -971, -971, -971, 431, -971, -971, 435,
+ 433, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, 442, 443, -971, -971, -971, -971,
+ 444, 448, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, 120, -971, -971, -971, 449, -971,
+ -971, 450, -971, 451, 452, -971, -971, 454, 456, -971,
+ -971, -971, -971, -971, -971, -971, 121, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, 458, 128, -971, -971, -971, -971,
+ 38, 38, -971, 263, 459, -971, -971, 462, 466, 472,
+ 274, 277, 278, 477, 480, 482, 483, 484, 489, 295,
+ 298, 299, 302, 303, 290, 308, 309, 316, 306, 311,
+ 515, 318, 320, 319, 321, 322, 521, 526, 527, 333,
+ 334, 335, 528, 538, 539, 341, 542, 544, 545, 546,
+ 346, 349, 351, 556, 557, 560, 562, 563, 365, 567,
+ 568, 570, 574, 575, 578, 378, 379, 399, 586, 587,
+ -971, 132, -971, 588, 601, 602, 407, 432, 410, 411,
+ 126, -971, 627, 628, 630, 632, 634, 636, 436, 642,
+ 643, 644, 294, -971, 645, -50, -971, 646, 648, 649,
+ 650, 651, 652, 655, 656, -971, 315, -971, 657, 658,
+ 460, 667, 671, 673, 473, -971, 80, 675, 475, 476,
+ -971, 200, 678, 679, 179, -971, 479, 685, 686, 488,
+ 688, 490, 502, 702, 703, 503, 516, 718, 721, 722,
+ 723, 617, -971, 728, 530, 77, -971, -971, -971, 730,
+ 729, 732, 734, 736, -971, -971, -971, 533, 543, 548,
+ 737, 739, 746, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, 551, -971, -971, -971, -971, -971,
+ -94, 554, 558, -971, -971, -971, 753, 754, 756, -971,
+ 757, 758, 564, 231, -971, -971, -971, 759, 762, 763,
+ 764, 777, -971, 778, 779, 780, 781, 590, 591, -971,
+ -971, -971, 765, 782, -971, 789, 172, 301, -971, -971,
+ -971, -971, -971, 593, 594, 595, 793, 597, 598, -971,
+ 789, 599, 795, -971, 603, -971, 789, 604, 605, 608,
+ 609, 610, 611, 612, -971, 613, 614, -971, 615, 616,
+ 618, -971, -971, 619, -971, -971, -971, 620, 758, -971,
+ -971, 621, 622, -971, 623, -971, -971, 13, 659, -971,
+ -971, -94, 624, 626, 629, -971, 798, -971, -971, 38,
+ 132, 77, 126, 802, -971, -971, -971, 552, 552, 797,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, 823,
+ 825, 826, 827, -971, -971, -971, -971, -971, -971, -971,
+ -971, -17, 828, 829, 831, 119, 82, 184, 147, 617,
+ -971, -971, 832, -144, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, 833, -971, -971, -971, -971,
+ 122, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, 797,
+ -971, 136, 151, 170, -971, 171, -971, -971, -971, -971,
+ -971, -971, 837, 838, 839, 840, 841, 842, 843, -971,
+ 844, -971, -971, -971, -971, -971, 178, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, 213, -971, 845, 846,
+ -971, -971, 847, 849, -971, -971, 848, 852, -971, -971,
+ 850, 854, -971, -971, 853, 855, -971, -971, -971, -971,
+ -971, -971, 94, -971, -971, -971, -971, -971, -971, -971,
+ 150, -971, -971, 856, 857, -971, -971, 858, 860, -971,
+ 861, 862, 863, 864, 865, 866, 215, -971, -971, -971,
+ -971, -971, -971, -971, 867, 868, 869, -971, 257, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ 259, -971, -971, -971, 870, -971, 871, -971, -971, -971,
+ 261, -971, -971, -971, -971, -971, 268, -971, 185, -971,
+ 872, -971, 269, -971, -971, 676, -971, 873, 874, -971,
+ -971, -971, -971, 875, 876, -971, -971, -971, 877, 802,
+ -971, 880, 881, 882, 883, 647, 683, 677, 684, 687,
+ 689, 690, 887, 691, 891, 892, 893, 894, 552, -971,
+ -971, 552, -971, 797, 294, -971, 823, 80, -971, 825,
+ 200, -971, 826, 635, -971, 827, -17, -971, 555, 828,
+ -971, 315, -971, 829, -84, -971, 831, 696, 697, 698,
+ 699, 700, 701, 119, -971, 704, 705, 708, 82, -971,
+ 901, 902, 184, -971, 707, 910, 712, 916, 147, -971,
+ -971, 135, 832, -971, 717, -144, -971, -971, 919, 927,
+ -50, -971, 833, 942, -971, -971, 747, -971, 305, 750,
+ 772, 783, -971, -971, -971, -971, -971, -971, -971, 331,
+ -971, 787, 788, 799, 800, -971, 280, -971, 292, -971,
+ 923, -971, 956, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, 293, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, 977,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, 974, 1012, -971, -971, -971, -971, -971, 1037,
+ -971, 310, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, 859, 878, -971, -971, 879, -971,
+ 38, -971, -971, 1045, -971, -971, -971, -971, -971, 312,
+ -971, -971, -971, -971, -971, -971, -971, -971, 884, 327,
+ -971, 789, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, 635, -971, 1046, 851, -971, 555, -971, -971,
+ -971, -971, -971, -971, 1050, 885, 1051, 135, -971, -971,
+ -971, -971, -971, 888, -971, -971, 1052, -971, 889, -971,
+ -971, 1053, -971, -971, 347, -971, -100, 1053, -971, -971,
+ 1054, 1055, 1058, -971, 330, -971, -971, -971, -971, -971,
+ -971, -971, 1059, 890, 886, 895, 1060, -100, -971, 897,
+ -971, -971, -971, 899, -971, -971, -971
+ };
+
+ const short
+ Dhcp4Parser::yydefact_[] =
+ {
+ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18,
+ 20, 22, 24, 26, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 44,
+ 36, 32, 31, 28, 29, 30, 35, 3, 33, 34,
+ 59, 5, 65, 7, 192, 9, 361, 11, 569, 13,
+ 596, 15, 489, 17, 498, 19, 537, 21, 323, 23,
+ 710, 25, 761, 27, 46, 39, 0, 0, 0, 0,
+ 0, 598, 0, 500, 539, 0, 0, 0, 48, 0,
+ 47, 0, 0, 40, 61, 0, 63, 759, 177, 210,
+ 0, 0, 0, 618, 620, 622, 208, 218, 220, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 145, 0, 0, 0, 0, 0, 154, 161, 163, 0,
+ 0, 0, 352, 487, 528, 0, 435, 583, 585, 428,
+ 0, 0, 0, 285, 646, 587, 314, 335, 0, 300,
+ 677, 691, 708, 167, 169, 0, 0, 0, 771, 813,
+ 0, 133, 0, 67, 70, 71, 72, 73, 74, 108,
+ 109, 110, 111, 112, 75, 103, 132, 92, 93, 94,
+ 116, 117, 118, 119, 120, 121, 122, 123, 114, 115,
+ 124, 125, 126, 128, 129, 130, 78, 79, 100, 80,
+ 81, 82, 127, 86, 87, 76, 105, 106, 107, 104,
+ 77, 84, 85, 98, 99, 101, 95, 96, 97, 83,
+ 88, 89, 90, 91, 102, 113, 131, 194, 196, 200,
+ 0, 0, 0, 0, 191, 0, 179, 182, 183, 184,
+ 185, 186, 187, 188, 189, 190, 413, 415, 417, 560,
+ 411, 419, 0, 423, 421, 642, 410, 366, 367, 368,
+ 369, 370, 394, 395, 396, 397, 398, 384, 385, 399,
+ 400, 401, 402, 403, 404, 405, 406, 407, 408, 409,
+ 0, 363, 373, 389, 390, 391, 374, 376, 377, 380,
+ 381, 382, 379, 375, 371, 372, 392, 393, 378, 386,
+ 387, 388, 383, 581, 580, 576, 577, 575, 0, 571,
+ 574, 578, 579, 640, 628, 630, 634, 632, 638, 636,
+ 624, 617, 611, 615, 616, 0, 599, 600, 612, 613,
+ 614, 608, 603, 609, 605, 606, 607, 610, 604, 0,
+ 518, 263, 0, 522, 520, 525, 0, 514, 515, 0,
+ 501, 502, 505, 517, 506, 507, 508, 524, 509, 510,
+ 511, 512, 513, 554, 0, 0, 552, 553, 556, 557,
+ 0, 540, 541, 544, 545, 546, 547, 548, 549, 550,
+ 551, 331, 333, 328, 0, 325, 329, 330, 0, 747,
+ 734, 0, 737, 0, 0, 741, 745, 0, 0, 751,
+ 753, 755, 757, 732, 730, 731, 0, 712, 715, 716,
+ 717, 718, 719, 720, 721, 722, 727, 723, 724, 725,
+ 726, 728, 729, 768, 0, 0, 763, 766, 767, 45,
+ 50, 0, 37, 43, 0, 64, 60, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 58, 69, 66, 0, 0, 0, 0, 0, 0, 0,
+ 181, 193, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 365, 362, 0, 573, 570, 0, 0, 0,
+ 0, 0, 0, 0, 0, 597, 602, 490, 0, 0,
+ 0, 0, 0, 0, 0, 499, 504, 0, 0, 0,
+ 538, 543, 0, 0, 327, 324, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 714, 711, 0, 0, 765, 762, 49, 41, 0,
+ 0, 0, 0, 0, 148, 149, 150, 0, 0, 0,
+ 0, 0, 0, 134, 135, 136, 137, 138, 139, 140,
+ 141, 142, 143, 144, 0, 172, 173, 151, 152, 153,
+ 0, 0, 0, 165, 166, 171, 0, 0, 0, 147,
+ 0, 0, 0, 0, 425, 426, 427, 0, 0, 0,
+ 0, 0, 676, 0, 0, 0, 0, 0, 0, 174,
+ 175, 176, 0, 0, 68, 0, 0, 0, 204, 205,
+ 206, 207, 180, 0, 0, 0, 0, 0, 0, 434,
+ 0, 0, 0, 364, 0, 572, 0, 0, 0, 0,
+ 0, 0, 0, 0, 601, 0, 0, 516, 0, 0,
+ 0, 527, 503, 0, 558, 559, 542, 0, 0, 326,
+ 733, 0, 0, 736, 0, 739, 740, 0, 0, 749,
+ 750, 0, 0, 0, 0, 713, 0, 770, 764, 0,
+ 0, 0, 0, 0, 619, 621, 623, 0, 0, 222,
+ 146, 156, 157, 158, 159, 160, 155, 162, 164, 354,
+ 491, 530, 437, 38, 584, 586, 430, 431, 432, 433,
+ 429, 0, 0, 589, 316, 0, 0, 0, 0, 0,
+ 168, 170, 0, 0, 51, 195, 198, 199, 197, 202,
+ 203, 201, 414, 416, 418, 562, 412, 420, 424, 422,
+ 0, 582, 641, 629, 631, 635, 633, 639, 637, 625,
+ 519, 264, 523, 521, 526, 555, 332, 334, 748, 735,
+ 738, 743, 744, 742, 746, 752, 754, 756, 758, 222,
+ 42, 0, 0, 0, 216, 0, 212, 215, 251, 256,
+ 258, 260, 0, 0, 0, 0, 0, 0, 0, 271,
+ 0, 277, 279, 281, 283, 250, 0, 229, 232, 233,
+ 234, 235, 236, 237, 238, 239, 240, 241, 242, 243,
+ 244, 245, 246, 247, 248, 249, 0, 227, 0, 223,
+ 224, 359, 0, 355, 356, 496, 0, 492, 493, 535,
+ 0, 531, 532, 442, 0, 438, 439, 295, 296, 297,
+ 298, 299, 0, 287, 290, 291, 292, 293, 294, 651,
+ 0, 648, 594, 0, 590, 591, 321, 0, 317, 318,
+ 0, 0, 0, 0, 0, 0, 0, 337, 340, 341,
+ 342, 343, 344, 345, 0, 0, 0, 310, 0, 302,
+ 305, 306, 307, 308, 309, 687, 689, 686, 684, 685,
+ 0, 679, 682, 683, 0, 703, 0, 706, 699, 700,
+ 0, 693, 696, 697, 698, 701, 0, 776, 0, 773,
+ 0, 819, 0, 815, 818, 53, 567, 0, 563, 564,
+ 626, 644, 645, 0, 0, 62, 760, 178, 0, 214,
+ 211, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 231, 209,
+ 219, 0, 221, 226, 0, 353, 358, 500, 488, 495,
+ 539, 529, 534, 0, 436, 441, 289, 286, 653, 650,
+ 647, 598, 588, 593, 0, 315, 320, 0, 0, 0,
+ 0, 0, 0, 339, 336, 0, 0, 0, 304, 301,
+ 0, 0, 681, 678, 0, 0, 0, 0, 695, 692,
+ 709, 0, 775, 772, 0, 817, 814, 55, 0, 54,
+ 0, 561, 566, 0, 643, 769, 0, 213, 0, 0,
+ 0, 0, 262, 265, 266, 267, 268, 269, 270, 0,
+ 276, 0, 0, 0, 0, 230, 0, 225, 0, 357,
+ 0, 494, 0, 533, 486, 465, 466, 467, 450, 451,
+ 470, 471, 472, 473, 474, 453, 454, 475, 476, 477,
+ 478, 479, 480, 481, 482, 483, 484, 485, 447, 448,
+ 449, 463, 464, 460, 461, 462, 459, 0, 444, 452,
+ 468, 469, 455, 456, 457, 458, 440, 288, 673, 0,
+ 668, 669, 670, 671, 672, 661, 662, 666, 667, 663,
+ 664, 665, 0, 654, 655, 658, 659, 660, 649, 0,
+ 592, 0, 319, 346, 347, 348, 349, 350, 351, 338,
+ 311, 312, 313, 303, 0, 0, 680, 702, 0, 705,
+ 0, 694, 791, 0, 789, 787, 781, 785, 786, 0,
+ 778, 783, 784, 782, 774, 820, 816, 52, 57, 0,
+ 565, 0, 217, 253, 254, 255, 252, 257, 259, 261,
+ 273, 274, 275, 272, 278, 280, 282, 284, 228, 360,
+ 497, 536, 446, 443, 0, 0, 652, 657, 595, 322,
+ 688, 690, 704, 707, 0, 0, 0, 780, 777, 56,
+ 568, 627, 445, 0, 675, 656, 0, 788, 0, 779,
+ 674, 0, 790, 796, 0, 793, 0, 795, 792, 806,
+ 0, 0, 0, 811, 0, 798, 801, 802, 803, 804,
+ 805, 794, 0, 0, 0, 0, 0, 800, 797, 0,
+ 808, 809, 810, 0, 799, 807, 812
+ };
+
+ const short
+ Dhcp4Parser::yypgoto_[] =
+ {
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -54, -971, -583, -971, 183,
+ -971, -971, -971, -971, -971, -971, -635, -971, -971, -971,
+ -67, -971, -971, -971, -971, -971, -971, -971, 366, 576,
+ 4, 10, 23, -40, -23, -12, 22, 25, 29, 33,
+ -971, -971, -971, -971, -971, 35, 40, 43, 45, 46,
+ 47, -971, 374, 50, -971, 51, -971, 53, 57, 58,
+ -971, 61, -971, 63, -971, -971, -971, -971, -971, -971,
+ -971, 367, 571, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, 123, -971, -971, -971, -971, -971, -971, 279,
+ -971, 97, -971, -696, 105, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -33, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, 88, -971,
+ -971, -971, -971, -971, -971, -971, -971, 67, -971, -971,
+ -971, -971, -971, -971, -971, 81, -971, -971, -971, 84,
+ 532, -971, -971, -971, -971, -971, -971, -971, 76, -971,
+ -971, -971, -971, -971, -971, -970, -971, -971, -971, 107,
+ -971, -971, -971, 108, 572, -971, -971, -971, -971, -971,
+ -971, -971, -971, -959, -971, -65, -971, 70, -971, 64,
+ 65, 68, 69, -971, -971, -971, -971, -971, -971, -971,
+ 100, -971, -971, -105, -46, -971, -971, -971, -971, -971,
+ 113, -971, -971, -971, 116, -971, 561, -971, -63, -971,
+ -971, -971, -971, -971, -42, -971, -971, -971, -971, -971,
+ -35, -971, -971, -971, 112, -971, -971, -971, 124, -971,
+ 565, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, 71, -971, -971, -971, 72, 592, -971, -971,
+ -51, -971, -8, -971, -39, -971, -971, -971, 115, -971,
+ -971, -971, 127, -971, 579, -55, -971, -15, -971, 3,
+ -971, 350, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -956,
+ -971, -971, -971, -971, -971, 130, -971, -971, -971, -88,
+ -971, -971, -971, -971, -971, -971, -971, -971, 99, -971,
+ -971, -971, -971, -971, -971, -971, 95, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, 375, 559, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ -971, -971, -971, -971, -971, -971, -971, -971, 414, 547,
+ -971, -971, -971, -971, -971, -971, 101, -971, -971, -91,
+ -971, -971, -971, -971, -971, -971, -110, -971, -971, -126,
+ -971, -971, -971, -971, -971, -971, -971, -971, -971, -971,
+ 103, -971
+ };
+
+ const short
+ Dhcp4Parser::yydefgoto_[] =
+ {
+ 0, 14, 15, 16, 17, 18, 19, 20, 21, 22,
+ 23, 24, 25, 26, 27, 36, 37, 38, 65, 724,
+ 82, 83, 39, 64, 79, 80, 745, 935, 1028, 1029,
+ 815, 41, 66, 85, 424, 86, 43, 67, 152, 153,
+ 154, 155, 156, 157, 158, 159, 160, 161, 162, 163,
+ 164, 165, 450, 166, 167, 168, 169, 170, 171, 172,
+ 173, 456, 716, 174, 457, 175, 458, 176, 177, 178,
+ 483, 179, 484, 180, 181, 182, 183, 184, 185, 186,
+ 428, 225, 226, 45, 68, 227, 493, 228, 494, 748,
+ 229, 495, 751, 230, 231, 232, 233, 187, 436, 188,
+ 429, 795, 796, 797, 948, 189, 437, 190, 438, 838,
+ 839, 840, 971, 816, 817, 818, 951, 1176, 819, 952,
+ 820, 953, 821, 954, 822, 823, 529, 824, 825, 826,
+ 827, 828, 829, 830, 962, 1183, 831, 832, 964, 833,
+ 965, 834, 966, 835, 967, 191, 473, 862, 863, 864,
+ 865, 866, 867, 868, 192, 479, 898, 899, 900, 901,
+ 902, 193, 476, 877, 878, 879, 994, 59, 75, 374,
+ 375, 376, 542, 377, 543, 194, 477, 886, 887, 888,
+ 889, 890, 891, 892, 893, 195, 462, 842, 843, 844,
+ 974, 47, 69, 270, 271, 272, 506, 273, 502, 274,
+ 503, 275, 504, 276, 507, 277, 510, 278, 509, 196,
+ 197, 198, 199, 469, 730, 283, 200, 466, 854, 855,
+ 856, 983, 1097, 1098, 201, 463, 53, 72, 846, 847,
+ 848, 977, 55, 73, 339, 340, 341, 342, 343, 344,
+ 345, 528, 346, 532, 347, 531, 348, 349, 533, 350,
+ 202, 464, 850, 851, 852, 980, 57, 74, 360, 361,
+ 362, 363, 364, 537, 365, 366, 367, 368, 285, 505,
+ 937, 938, 939, 1030, 49, 70, 298, 299, 300, 514,
+ 203, 467, 204, 468, 205, 475, 873, 874, 875, 991,
+ 51, 71, 315, 316, 317, 206, 433, 207, 434, 208,
+ 435, 321, 524, 942, 1033, 322, 518, 323, 519, 324,
+ 521, 325, 520, 326, 523, 327, 522, 328, 517, 292,
+ 511, 943, 209, 474, 870, 871, 988, 1122, 1123, 1124,
+ 1125, 1126, 1194, 1127, 210, 211, 480, 910, 911, 912,
+ 1010, 913, 1011, 212, 481, 920, 921, 922, 923, 1015,
+ 924, 925, 1017, 213, 482, 61, 76, 396, 397, 398,
+ 399, 548, 400, 401, 550, 402, 403, 404, 553, 783,
+ 405, 554, 406, 547, 407, 408, 409, 557, 410, 558,
+ 411, 559, 412, 560, 214, 427, 63, 77, 415, 416,
+ 417, 563, 418, 215, 488, 928, 929, 1021, 1159, 1160,
+ 1161, 1162, 1206, 1163, 1204, 1224, 1225, 1226, 1234, 1235,
+ 1236, 1242, 1237, 1238, 1239, 1240, 1246, 216, 489, 932,
+ 933, 934
+ };
+
+ const short
+ Dhcp4Parser::yytable_[] =
+ {
+ 151, 224, 246, 294, 311, 295, 337, 356, 373, 393,
+ 78, 358, 836, 1089, 289, 758, 318, 234, 286, 301,
+ 313, 762, 351, 369, 1090, 394, 329, 1105, 723, 250,
+ 288, 781, 359, 40, 284, 297, 312, 425, 124, 28,
+ 338, 357, 426, 29, 930, 30, 251, 31, 371, 372,
+ 81, 293, 127, 128, 290, 42, 319, 252, 150, 491,
+ 235, 287, 302, 314, 492, 352, 370, 243, 395, 44,
+ 244, 123, 291, 247, 320, 711, 712, 713, 714, 248,
+ 1229, 46, 500, 1230, 1231, 1232, 1233, 501, 512, 413,
+ 414, 253, 249, 513, 254, 723, 515, 986, 255, 48,
+ 987, 516, 256, 50, 257, 857, 858, 859, 860, 258,
+ 861, 715, 259, 52, 260, 261, 262, 330, 150, 263,
+ 264, 54, 265, 544, 561, 56, 266, 267, 545, 562,
+ 268, 565, 269, 279, 280, 84, 566, 281, 282, 491,
+ 296, 217, 218, 87, 945, 219, 88, 58, 220, 221,
+ 222, 223, 150, 989, 565, 60, 990, 420, 89, 946,
+ 90, 91, 92, 93, 94, 95, 96, 97, 98, 331,
+ 62, 332, 333, 500, 949, 334, 335, 336, 947, 950,
+ 782, 968, 127, 128, 127, 128, 969, 419, 1022, 746,
+ 747, 1023, 99, 100, 101, 102, 103, 104, 105, 106,
+ 107, 108, 109, 110, 111, 112, 113, 114, 115, 116,
+ 117, 118, 119, 120, 121, 122, 968, 422, 1003, 123,
+ 124, 970, 1089, 1004, 331, 894, 895, 896, 127, 128,
+ 125, 126, 421, 1090, 127, 128, 1105, 127, 128, 129,
+ 32, 33, 34, 35, 130, 131, 132, 133, 134, 127,
+ 128, 310, 940, 135, 880, 881, 882, 883, 884, 885,
+ 1008, 423, 1012, 136, 1018, 1009, 137, 1013, 430, 1019,
+ 431, 561, 1025, 138, 139, 1056, 1020, 1026, 140, 432,
+ 439, 141, 150, 968, 150, 142, 127, 128, 1188, 331,
+ 353, 332, 333, 354, 355, 512, 1192, 914, 915, 916,
+ 1189, 1193, 127, 128, 440, 143, 144, 145, 146, 147,
+ 148, 371, 372, 544, 1152, 1207, 1153, 1154, 1199, 149,
+ 1208, 749, 750, 91, 92, 93, 94, 95, 150, 441,
+ 515, 905, 906, 1247, 150, 1210, 442, 150, 1248, 726,
+ 727, 728, 729, 1173, 1174, 1175, 93, 94, 95, 917,
+ 1227, 525, 443, 1228, 99, 100, 101, 102, 103, 104,
+ 105, 106, 107, 108, 444, 445, 567, 568, 113, 114,
+ 115, 116, 117, 118, 119, 120, 121, 446, 236, 237,
+ 238, 447, 124, 1180, 1181, 1182, 150, 448, 449, 451,
+ 452, 453, 454, 455, 239, 459, 127, 128, 240, 241,
+ 242, 129, 150, 124, 460, 461, 130, 131, 132, 465,
+ 470, 243, 471, 472, 244, 135, 478, 127, 128, 485,
+ 486, 487, 245, 490, 151, 496, 497, 498, 499, 508,
+ 526, 303, 530, 224, 527, 534, 536, 304, 305, 306,
+ 307, 308, 309, 535, 310, 246, 538, 539, 294, 234,
+ 295, 541, 540, 546, 549, 551, 552, 289, 555, 311,
+ 556, 286, 564, 570, 301, 569, 571, 143, 144, 337,
+ 572, 318, 250, 288, 356, 313, 573, 284, 358, 574,
+ 297, 577, 575, 576, 578, 351, 579, 580, 581, 251,
+ 369, 312, 235, 582, 393, 588, 150, 290, 583, 359,
+ 252, 584, 585, 338, 287, 586, 587, 302, 357, 592,
+ 394, 319, 589, 590, 593, 291, 247, 150, 314, 594,
+ 591, 595, 248, 596, 597, 600, 598, 599, 352, 320,
+ 601, 602, 606, 370, 253, 249, 1211, 254, 603, 604,
+ 605, 255, 607, 608, 609, 256, 610, 257, 611, 612,
+ 613, 614, 258, 395, 615, 259, 616, 260, 261, 262,
+ 617, 618, 263, 264, 619, 265, 620, 621, 622, 266,
+ 267, 623, 624, 268, 625, 269, 279, 280, 626, 627,
+ 281, 282, 628, 629, 630, 296, 93, 94, 95, 798,
+ 632, 633, 635, 799, 800, 801, 802, 803, 804, 805,
+ 806, 807, 808, 809, 631, 636, 637, 810, 811, 812,
+ 813, 814, 638, 640, 641, 99, 100, 101, 1, 2,
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
+ 13, 643, 644, 151, 645, 224, 646, 639, 647, 649,
+ 648, 331, 123, 124, 331, 790, 650, 651, 652, 654,
+ 656, 234, 657, 658, 659, 660, 661, 127, 128, 662,
+ 663, 665, 666, 667, 91, 92, 93, 94, 95, 897,
+ 907, 668, 393, 1108, 1109, 669, 931, 670, 671, 673,
+ 674, 675, 677, 678, 680, 903, 908, 918, 394, 681,
+ 682, 683, 684, 685, 235, 99, 100, 101, 102, 103,
+ 104, 105, 106, 107, 108, 686, 687, 688, 689, 113,
+ 114, 115, 116, 117, 118, 119, 120, 121, 122, 127,
+ 128, 690, 691, 124, 331, 692, 693, 694, 904, 909,
+ 919, 395, 696, 697, 699, 704, 700, 127, 128, 701,
+ 241, 702, 129, 703, 707, 705, 708, 130, 131, 132,
+ 706, 709, 243, 710, 150, 244, 717, 150, 719, 720,
+ 718, 721, 722, 245, 731, 30, 725, 732, 733, 734,
+ 742, 378, 379, 380, 381, 382, 383, 384, 385, 386,
+ 387, 388, 389, 390, 735, 736, 737, 738, 739, 743,
+ 391, 392, 740, 741, 744, 752, 753, 754, 755, 756,
+ 757, 759, 760, 789, 837, 761, 763, 764, 143, 144,
+ 765, 766, 767, 768, 769, 770, 771, 772, 773, 150,
+ 774, 775, 776, 778, 779, 780, 786, 784, 787, 794,
+ 841, 788, 845, 849, 853, 869, 872, 150, 876, 927,
+ 936, 955, 956, 957, 958, 959, 960, 961, 963, 973,
+ 1042, 972, 976, 975, 978, 979, 981, 982, 985, 984,
+ 993, 777, 992, 996, 995, 997, 998, 999, 1000, 1001,
+ 1002, 1005, 1006, 1007, 1014, 1016, 1024, 1032, 1027, 1031,
+ 1044, 1036, 1035, 1034, 1038, 1039, 1040, 1041, 1043, 1045,
+ 1046, 1049, 1047, 1048, 1050, 1051, 1052, 1053, 1054, 1133,
+ 1134, 1135, 1136, 1137, 1138, 1144, 1145, 246, 1141, 1140,
+ 337, 1142, 1147, 356, 1148, 1149, 1064, 358, 1091, 289,
+ 1150, 1110, 1165, 286, 311, 1167, 351, 373, 1102, 369,
+ 1168, 1190, 1100, 1119, 250, 288, 318, 1117, 359, 284,
+ 313, 897, 1115, 1068, 338, 907, 1171, 357, 1099, 1172,
+ 1088, 251, 1177, 1116, 1155, 1114, 312, 903, 931, 290,
+ 1069, 908, 252, 294, 1191, 295, 287, 918, 1103, 352,
+ 1157, 1070, 370, 1120, 1178, 1101, 319, 291, 247, 301,
+ 1118, 1195, 1196, 314, 248, 1179, 1104, 1065, 1156, 1184,
+ 1185, 1121, 1111, 1066, 320, 297, 253, 249, 1112, 254,
+ 904, 1186, 1187, 255, 909, 1071, 1067, 256, 1072, 257,
+ 919, 1113, 1073, 1158, 258, 1197, 1074, 259, 1075, 260,
+ 261, 262, 302, 1076, 263, 264, 1077, 265, 1078, 1079,
+ 1080, 266, 267, 1081, 1082, 268, 1083, 269, 279, 280,
+ 1084, 1085, 281, 282, 1086, 1198, 1087, 1093, 1094, 1205,
+ 1213, 1095, 1096, 1092, 1216, 1218, 1214, 1221, 1243, 1244,
+ 1223, 1200, 1245, 1249, 1253, 785, 791, 634, 944, 793,
+ 1057, 642, 1037, 1055, 1107, 1143, 679, 1132, 1131, 1139,
+ 1201, 1202, 1058, 1059, 653, 1106, 1209, 1212, 1217, 1251,
+ 1220, 1222, 1061, 1060, 1063, 1250, 1203, 672, 1252, 1255,
+ 296, 1256, 1169, 1170, 1062, 664, 676, 655, 1130, 1215,
+ 941, 1146, 698, 1151, 926, 792, 1219, 1241, 1129, 1128,
+ 695, 1254, 0, 1164, 0, 1064, 0, 1091, 1166, 0,
+ 1110, 0, 0, 0, 0, 0, 0, 1102, 0, 0,
+ 1155, 1100, 1119, 0, 0, 0, 1117, 0, 0, 0,
+ 0, 1115, 1068, 0, 0, 0, 1157, 1099, 0, 1088,
+ 0, 0, 1116, 0, 1114, 0, 0, 0, 0, 1069,
+ 0, 0, 0, 0, 1156, 0, 0, 1103, 0, 0,
+ 1070, 0, 1120, 0, 1101, 0, 0, 0, 0, 1118,
+ 0, 0, 0, 0, 0, 1104, 1065, 0, 0, 1158,
+ 1121, 1111, 1066, 0, 0, 0, 0, 1112, 0, 0,
+ 0, 0, 0, 0, 1071, 1067, 0, 1072, 0, 0,
+ 1113, 1073, 0, 0, 0, 1074, 0, 1075, 0, 0,
+ 0, 0, 1076, 0, 0, 1077, 0, 1078, 1079, 1080,
+ 0, 0, 1081, 1082, 0, 1083, 0, 0, 0, 1084,
+ 1085, 0, 0, 1086, 0, 1087, 1093, 1094, 0, 0,
+ 1095, 1096, 1092
+ };
+
+ const short
+ Dhcp4Parser::yycheck_[] =
+ {
+ 67, 68, 69, 70, 71, 70, 73, 74, 75, 76,
+ 64, 74, 708, 983, 69, 650, 71, 68, 69, 70,
+ 71, 656, 73, 74, 983, 76, 72, 983, 611, 69,
+ 69, 18, 74, 7, 69, 70, 71, 3, 88, 0,
+ 73, 74, 8, 5, 188, 7, 69, 9, 132, 133,
+ 202, 101, 102, 103, 69, 7, 71, 69, 202, 3,
+ 68, 69, 70, 71, 8, 73, 74, 117, 76, 7,
+ 120, 87, 69, 69, 71, 169, 170, 171, 172, 69,
+ 180, 7, 3, 183, 184, 185, 186, 8, 3, 12,
+ 13, 69, 69, 8, 69, 678, 3, 3, 69, 7,
+ 6, 8, 69, 7, 69, 122, 123, 124, 125, 69,
+ 127, 205, 69, 7, 69, 69, 69, 37, 202, 69,
+ 69, 7, 69, 3, 3, 7, 69, 69, 8, 8,
+ 69, 3, 69, 69, 69, 10, 8, 69, 69, 3,
+ 70, 15, 16, 11, 8, 19, 14, 7, 22, 23,
+ 24, 25, 202, 3, 3, 7, 6, 3, 26, 8,
+ 28, 29, 30, 31, 32, 33, 34, 35, 36, 89,
+ 7, 91, 92, 3, 3, 95, 96, 97, 8, 8,
+ 167, 3, 102, 103, 102, 103, 8, 6, 3, 17,
+ 18, 6, 60, 61, 62, 63, 64, 65, 66, 67,
+ 68, 69, 70, 71, 72, 73, 74, 75, 76, 77,
+ 78, 79, 80, 81, 82, 83, 3, 8, 3, 87,
+ 88, 8, 1192, 8, 89, 143, 144, 145, 102, 103,
+ 98, 99, 4, 1192, 102, 103, 1192, 102, 103, 107,
+ 202, 203, 204, 205, 112, 113, 114, 115, 116, 102,
+ 103, 129, 130, 121, 135, 136, 137, 138, 139, 140,
+ 3, 3, 3, 131, 3, 8, 134, 8, 4, 8,
+ 4, 3, 3, 141, 142, 971, 8, 8, 146, 4,
+ 4, 149, 202, 3, 202, 153, 102, 103, 8, 89,
+ 90, 91, 92, 93, 94, 3, 3, 150, 151, 152,
+ 8, 8, 102, 103, 4, 173, 174, 175, 176, 177,
+ 178, 132, 133, 3, 179, 3, 181, 182, 8, 187,
+ 8, 20, 21, 29, 30, 31, 32, 33, 202, 4,
+ 3, 147, 148, 3, 202, 8, 4, 202, 8, 108,
+ 109, 110, 111, 38, 39, 40, 31, 32, 33, 202,
+ 3, 8, 4, 6, 60, 61, 62, 63, 64, 65,
+ 66, 67, 68, 69, 4, 4, 420, 421, 74, 75,
+ 76, 77, 78, 79, 80, 81, 82, 4, 84, 85,
+ 86, 4, 88, 52, 53, 54, 202, 4, 4, 4,
+ 4, 4, 4, 4, 100, 4, 102, 103, 104, 105,
+ 106, 107, 202, 88, 4, 4, 112, 113, 114, 4,
+ 4, 117, 4, 4, 120, 121, 4, 102, 103, 4,
+ 4, 4, 128, 4, 491, 4, 4, 4, 4, 4,
+ 3, 116, 4, 500, 8, 4, 3, 122, 123, 124,
+ 125, 126, 127, 8, 129, 512, 4, 4, 515, 500,
+ 515, 3, 8, 4, 4, 4, 4, 512, 4, 526,
+ 4, 512, 4, 4, 515, 202, 4, 173, 174, 536,
+ 4, 526, 512, 512, 541, 526, 4, 512, 541, 205,
+ 515, 4, 205, 205, 4, 536, 4, 4, 4, 512,
+ 541, 526, 500, 4, 561, 205, 202, 512, 203, 541,
+ 512, 203, 203, 536, 512, 203, 203, 515, 541, 203,
+ 561, 526, 204, 204, 203, 512, 512, 202, 526, 4,
+ 204, 203, 512, 203, 205, 4, 205, 205, 536, 526,
+ 4, 4, 4, 541, 512, 512, 1171, 512, 205, 205,
+ 205, 512, 4, 4, 203, 512, 4, 512, 4, 4,
+ 4, 205, 512, 561, 205, 512, 205, 512, 512, 512,
+ 4, 4, 512, 512, 4, 512, 4, 4, 203, 512,
+ 512, 4, 4, 512, 4, 512, 512, 512, 4, 4,
+ 512, 512, 4, 205, 205, 515, 31, 32, 33, 37,
+ 4, 4, 4, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 205, 4, 4, 55, 56, 57,
+ 58, 59, 205, 203, 203, 60, 61, 62, 189, 190,
+ 191, 192, 193, 194, 195, 196, 197, 198, 199, 200,
+ 201, 4, 4, 700, 4, 702, 4, 205, 4, 203,
+ 4, 89, 87, 88, 89, 699, 4, 4, 4, 4,
+ 4, 702, 4, 4, 4, 4, 4, 102, 103, 4,
+ 4, 4, 4, 203, 29, 30, 31, 32, 33, 736,
+ 737, 4, 739, 118, 119, 4, 743, 4, 205, 4,
+ 205, 205, 4, 4, 205, 736, 737, 738, 739, 4,
+ 4, 203, 4, 203, 702, 60, 61, 62, 63, 64,
+ 65, 66, 67, 68, 69, 203, 4, 4, 205, 74,
+ 75, 76, 77, 78, 79, 80, 81, 82, 83, 102,
+ 103, 205, 4, 88, 89, 4, 4, 4, 736, 737,
+ 738, 739, 4, 203, 4, 202, 7, 102, 103, 7,
+ 105, 7, 107, 7, 7, 202, 7, 112, 113, 114,
+ 202, 5, 117, 202, 202, 120, 202, 202, 5, 5,
+ 202, 5, 5, 128, 5, 7, 202, 5, 5, 5,
+ 5, 154, 155, 156, 157, 158, 159, 160, 161, 162,
+ 163, 164, 165, 166, 7, 7, 7, 7, 7, 7,
+ 173, 174, 202, 202, 5, 202, 202, 202, 5, 202,
+ 202, 202, 7, 5, 7, 202, 202, 202, 173, 174,
+ 202, 202, 202, 202, 202, 202, 202, 202, 202, 202,
+ 202, 202, 202, 202, 202, 202, 202, 168, 202, 27,
+ 7, 202, 7, 7, 7, 7, 7, 202, 7, 7,
+ 7, 4, 4, 4, 4, 4, 4, 4, 4, 3,
+ 203, 6, 3, 6, 6, 3, 6, 3, 3, 6,
+ 3, 678, 6, 3, 6, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 3, 202, 6,
+ 203, 4, 6, 8, 4, 4, 4, 4, 205, 205,
+ 203, 4, 203, 203, 203, 4, 4, 4, 4, 203,
+ 203, 203, 203, 203, 203, 4, 4, 974, 203, 205,
+ 977, 203, 205, 980, 4, 203, 983, 980, 983, 974,
+ 4, 988, 205, 974, 991, 6, 977, 994, 983, 980,
+ 3, 8, 983, 988, 974, 974, 991, 988, 980, 974,
+ 991, 1008, 988, 983, 977, 1012, 4, 980, 983, 202,
+ 983, 974, 202, 988, 1021, 988, 991, 1008, 1025, 974,
+ 983, 1012, 974, 1030, 8, 1030, 974, 1018, 983, 977,
+ 1021, 983, 980, 988, 202, 983, 991, 974, 974, 1030,
+ 988, 4, 8, 991, 974, 202, 983, 983, 1021, 202,
+ 202, 988, 988, 983, 991, 1030, 974, 974, 988, 974,
+ 1008, 202, 202, 974, 1012, 983, 983, 974, 983, 974,
+ 1018, 988, 983, 1021, 974, 3, 983, 974, 983, 974,
+ 974, 974, 1030, 983, 974, 974, 983, 974, 983, 983,
+ 983, 974, 974, 983, 983, 974, 983, 974, 974, 974,
+ 983, 983, 974, 974, 983, 8, 983, 983, 983, 4,
+ 4, 983, 983, 983, 4, 4, 205, 5, 4, 4,
+ 7, 202, 4, 4, 4, 691, 700, 491, 789, 702,
+ 973, 500, 949, 968, 986, 1008, 544, 996, 994, 1003,
+ 202, 202, 974, 976, 512, 985, 202, 1192, 203, 203,
+ 202, 202, 979, 977, 982, 205, 1150, 536, 203, 202,
+ 1030, 202, 1030, 1032, 980, 526, 541, 515, 993, 1197,
+ 760, 1012, 565, 1018, 739, 701, 1207, 1227, 991, 989,
+ 561, 1247, -1, 1022, -1, 1192, -1, 1192, 1025, -1,
+ 1197, -1, -1, -1, -1, -1, -1, 1192, -1, -1,
+ 1207, 1192, 1197, -1, -1, -1, 1197, -1, -1, -1,
+ -1, 1197, 1192, -1, -1, -1, 1207, 1192, -1, 1192,
+ -1, -1, 1197, -1, 1197, -1, -1, -1, -1, 1192,
+ -1, -1, -1, -1, 1207, -1, -1, 1192, -1, -1,
+ 1192, -1, 1197, -1, 1192, -1, -1, -1, -1, 1197,
+ -1, -1, -1, -1, -1, 1192, 1192, -1, -1, 1207,
+ 1197, 1197, 1192, -1, -1, -1, -1, 1197, -1, -1,
+ -1, -1, -1, -1, 1192, 1192, -1, 1192, -1, -1,
+ 1197, 1192, -1, -1, -1, 1192, -1, 1192, -1, -1,
+ -1, -1, 1192, -1, -1, 1192, -1, 1192, 1192, 1192,
+ -1, -1, 1192, 1192, -1, 1192, -1, -1, -1, 1192,
+ 1192, -1, -1, 1192, -1, 1192, 1192, 1192, -1, -1,
+ 1192, 1192, 1192
+ };
+
+ const short
+ Dhcp4Parser::yystos_[] =
+ {
+ 0, 189, 190, 191, 192, 193, 194, 195, 196, 197,
+ 198, 199, 200, 201, 207, 208, 209, 210, 211, 212,
+ 213, 214, 215, 216, 217, 218, 219, 220, 0, 5,
+ 7, 9, 202, 203, 204, 205, 221, 222, 223, 228,
+ 7, 237, 7, 242, 7, 289, 7, 397, 7, 480,
+ 7, 496, 7, 432, 7, 438, 7, 462, 7, 373,
+ 7, 561, 7, 592, 229, 224, 238, 243, 290, 398,
+ 481, 497, 433, 439, 463, 374, 562, 593, 221, 230,
+ 231, 202, 226, 227, 10, 239, 241, 11, 14, 26,
+ 28, 29, 30, 31, 32, 33, 34, 35, 36, 60,
+ 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
+ 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
+ 81, 82, 83, 87, 88, 98, 99, 102, 103, 107,
+ 112, 113, 114, 115, 116, 121, 131, 134, 141, 142,
+ 146, 149, 153, 173, 174, 175, 176, 177, 178, 187,
+ 202, 236, 244, 245, 246, 247, 248, 249, 250, 251,
+ 252, 253, 254, 255, 256, 257, 259, 260, 261, 262,
+ 263, 264, 265, 266, 269, 271, 273, 274, 275, 277,
+ 279, 280, 281, 282, 283, 284, 285, 303, 305, 311,
+ 313, 351, 360, 367, 381, 391, 415, 416, 417, 418,
+ 422, 430, 456, 486, 488, 490, 501, 503, 505, 528,
+ 540, 541, 549, 559, 590, 599, 623, 15, 16, 19,
+ 22, 23, 24, 25, 236, 287, 288, 291, 293, 296,
+ 299, 300, 301, 302, 486, 488, 84, 85, 86, 100,
+ 104, 105, 106, 117, 120, 128, 236, 246, 247, 248,
+ 249, 250, 251, 252, 253, 254, 255, 261, 262, 263,
+ 264, 265, 266, 269, 271, 273, 274, 275, 277, 279,
+ 399, 400, 401, 403, 405, 407, 409, 411, 413, 415,
+ 416, 417, 418, 421, 456, 474, 486, 488, 490, 501,
+ 503, 505, 525, 101, 236, 411, 413, 456, 482, 483,
+ 484, 486, 488, 116, 122, 123, 124, 125, 126, 127,
+ 129, 236, 456, 486, 488, 498, 499, 500, 501, 503,
+ 505, 507, 511, 513, 515, 517, 519, 521, 523, 430,
+ 37, 89, 91, 92, 95, 96, 97, 236, 331, 440,
+ 441, 442, 443, 444, 445, 446, 448, 450, 452, 453,
+ 455, 486, 488, 90, 93, 94, 236, 331, 444, 450,
+ 464, 465, 466, 467, 468, 470, 471, 472, 473, 486,
+ 488, 132, 133, 236, 375, 376, 377, 379, 154, 155,
+ 156, 157, 158, 159, 160, 161, 162, 163, 164, 165,
+ 166, 173, 174, 236, 486, 488, 563, 564, 565, 566,
+ 568, 569, 571, 572, 573, 576, 578, 580, 581, 582,
+ 584, 586, 588, 12, 13, 594, 595, 596, 598, 6,
+ 3, 4, 8, 3, 240, 3, 8, 591, 286, 306,
+ 4, 4, 4, 502, 504, 506, 304, 312, 314, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 258, 4, 4, 4, 4, 4, 267, 270, 272, 4,
+ 4, 4, 392, 431, 457, 4, 423, 487, 489, 419,
+ 4, 4, 4, 352, 529, 491, 368, 382, 4, 361,
+ 542, 550, 560, 276, 278, 4, 4, 4, 600, 624,
+ 4, 3, 8, 292, 294, 297, 4, 4, 4, 4,
+ 3, 8, 404, 406, 408, 475, 402, 410, 4, 414,
+ 412, 526, 3, 8, 485, 3, 8, 524, 512, 514,
+ 518, 516, 522, 520, 508, 8, 3, 8, 447, 332,
+ 4, 451, 449, 454, 4, 8, 3, 469, 4, 4,
+ 8, 3, 378, 380, 3, 8, 4, 579, 567, 4,
+ 570, 4, 4, 574, 577, 4, 4, 583, 585, 587,
+ 589, 3, 8, 597, 4, 3, 8, 221, 221, 202,
+ 4, 4, 4, 4, 205, 205, 205, 4, 4, 4,
+ 4, 4, 4, 203, 203, 203, 203, 203, 205, 204,
+ 204, 204, 203, 203, 4, 203, 203, 205, 205, 205,
+ 4, 4, 4, 205, 205, 205, 4, 4, 4, 203,
+ 4, 4, 4, 4, 205, 205, 205, 4, 4, 4,
+ 4, 4, 203, 4, 4, 4, 4, 4, 4, 205,
+ 205, 205, 4, 4, 245, 4, 4, 4, 205, 205,
+ 203, 203, 288, 4, 4, 4, 4, 4, 4, 203,
+ 4, 4, 4, 400, 4, 483, 4, 4, 4, 4,
+ 4, 4, 4, 4, 500, 4, 4, 203, 4, 4,
+ 4, 205, 442, 4, 205, 205, 466, 4, 4, 376,
+ 205, 4, 4, 203, 4, 203, 203, 4, 4, 205,
+ 205, 4, 4, 4, 4, 564, 4, 203, 595, 4,
+ 7, 7, 7, 7, 202, 202, 202, 7, 7, 5,
+ 202, 169, 170, 171, 172, 205, 268, 202, 202, 5,
+ 5, 5, 5, 223, 225, 202, 108, 109, 110, 111,
+ 420, 5, 5, 5, 5, 7, 7, 7, 7, 7,
+ 202, 202, 5, 7, 5, 232, 17, 18, 295, 20,
+ 21, 298, 202, 202, 202, 5, 202, 202, 232, 202,
+ 7, 202, 232, 202, 202, 202, 202, 202, 202, 202,
+ 202, 202, 202, 202, 202, 202, 202, 225, 202, 202,
+ 202, 18, 167, 575, 168, 268, 202, 202, 202, 5,
+ 221, 244, 594, 287, 27, 307, 308, 309, 37, 41,
+ 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
+ 55, 56, 57, 58, 59, 236, 319, 320, 321, 324,
+ 326, 328, 330, 331, 333, 334, 335, 336, 337, 338,
+ 339, 342, 343, 345, 347, 349, 319, 7, 315, 316,
+ 317, 7, 393, 394, 395, 7, 434, 435, 436, 7,
+ 458, 459, 460, 7, 424, 425, 426, 122, 123, 124,
+ 125, 127, 353, 354, 355, 356, 357, 358, 359, 7,
+ 530, 531, 7, 492, 493, 494, 7, 369, 370, 371,
+ 135, 136, 137, 138, 139, 140, 383, 384, 385, 386,
+ 387, 388, 389, 390, 143, 144, 145, 236, 362, 363,
+ 364, 365, 366, 486, 488, 147, 148, 236, 486, 488,
+ 543, 544, 545, 547, 150, 151, 152, 202, 486, 488,
+ 551, 552, 553, 554, 556, 557, 563, 7, 601, 602,
+ 188, 236, 625, 626, 627, 233, 7, 476, 477, 478,
+ 130, 507, 509, 527, 315, 8, 8, 8, 310, 3,
+ 8, 322, 325, 327, 329, 4, 4, 4, 4, 4,
+ 4, 4, 340, 4, 344, 346, 348, 350, 3, 8,
+ 8, 318, 6, 3, 396, 6, 3, 437, 6, 3,
+ 461, 6, 3, 427, 6, 3, 3, 6, 532, 3,
+ 6, 495, 6, 3, 372, 6, 3, 4, 4, 4,
+ 4, 4, 4, 3, 8, 4, 4, 4, 3, 8,
+ 546, 548, 3, 8, 4, 555, 4, 558, 3, 8,
+ 8, 603, 3, 6, 4, 3, 8, 202, 234, 235,
+ 479, 6, 3, 510, 8, 6, 4, 308, 4, 4,
+ 4, 4, 203, 205, 203, 205, 203, 203, 203, 4,
+ 203, 4, 4, 4, 4, 320, 319, 317, 399, 395,
+ 440, 436, 464, 460, 236, 246, 247, 248, 249, 250,
+ 251, 252, 253, 254, 255, 261, 262, 263, 264, 265,
+ 266, 269, 271, 273, 274, 275, 277, 279, 331, 391,
+ 409, 411, 413, 415, 416, 417, 418, 428, 429, 456,
+ 486, 488, 501, 503, 505, 525, 426, 354, 118, 119,
+ 236, 246, 247, 248, 331, 430, 456, 486, 488, 501,
+ 503, 505, 533, 534, 535, 536, 537, 539, 531, 498,
+ 494, 375, 371, 203, 203, 203, 203, 203, 203, 384,
+ 205, 203, 203, 363, 4, 4, 544, 205, 4, 203,
+ 4, 552, 179, 181, 182, 236, 331, 486, 488, 604,
+ 605, 606, 607, 609, 602, 205, 626, 6, 3, 482,
+ 478, 4, 202, 38, 39, 40, 323, 202, 202, 202,
+ 52, 53, 54, 341, 202, 202, 202, 202, 8, 8,
+ 8, 8, 3, 8, 538, 4, 8, 3, 8, 8,
+ 202, 202, 202, 221, 610, 4, 608, 3, 8, 202,
+ 8, 232, 429, 4, 205, 535, 4, 203, 4, 605,
+ 202, 5, 202, 7, 611, 612, 613, 3, 6, 180,
+ 183, 184, 185, 186, 614, 615, 616, 618, 619, 620,
+ 621, 612, 617, 4, 4, 4, 622, 3, 8, 4,
+ 205, 203, 203, 4, 615, 202, 202
+ };
+
+ const short
+ Dhcp4Parser::yyr1_[] =
+ {
+ 0, 206, 208, 207, 209, 207, 210, 207, 211, 207,
+ 212, 207, 213, 207, 214, 207, 215, 207, 216, 207,
+ 217, 207, 218, 207, 219, 207, 220, 207, 221, 221,
+ 221, 221, 221, 221, 221, 222, 224, 223, 225, 226,
+ 226, 227, 227, 227, 229, 228, 230, 230, 231, 231,
+ 231, 233, 232, 234, 234, 235, 235, 235, 236, 238,
+ 237, 240, 239, 239, 241, 243, 242, 244, 244, 244,
+ 245, 245, 245, 245, 245, 245, 245, 245, 245, 245,
+ 245, 245, 245, 245, 245, 245, 245, 245, 245, 245,
+ 245, 245, 245, 245, 245, 245, 245, 245, 245, 245,
+ 245, 245, 245, 245, 245, 245, 245, 245, 245, 245,
+ 245, 245, 245, 245, 245, 245, 245, 245, 245, 245,
+ 245, 245, 245, 245, 245, 245, 245, 245, 245, 245,
+ 245, 245, 245, 245, 246, 247, 248, 249, 250, 251,
+ 252, 253, 254, 255, 256, 258, 257, 259, 260, 261,
+ 262, 263, 264, 265, 267, 266, 268, 268, 268, 268,
+ 268, 270, 269, 272, 271, 273, 274, 276, 275, 278,
+ 277, 279, 280, 281, 282, 283, 284, 286, 285, 287,
+ 287, 287, 288, 288, 288, 288, 288, 288, 288, 288,
+ 288, 288, 290, 289, 292, 291, 294, 293, 295, 295,
+ 297, 296, 298, 298, 299, 300, 301, 302, 304, 303,
+ 306, 305, 307, 307, 307, 308, 310, 309, 312, 311,
+ 314, 313, 315, 315, 316, 316, 316, 318, 317, 319,
+ 319, 319, 320, 320, 320, 320, 320, 320, 320, 320,
+ 320, 320, 320, 320, 320, 320, 320, 320, 320, 320,
+ 320, 322, 321, 323, 323, 323, 325, 324, 327, 326,
+ 329, 328, 330, 332, 331, 333, 334, 335, 336, 337,
+ 338, 340, 339, 341, 341, 341, 342, 344, 343, 346,
+ 345, 348, 347, 350, 349, 352, 351, 353, 353, 353,
+ 354, 354, 354, 354, 354, 355, 356, 357, 358, 359,
+ 361, 360, 362, 362, 362, 363, 363, 363, 363, 363,
+ 363, 364, 365, 366, 368, 367, 369, 369, 370, 370,
+ 370, 372, 371, 374, 373, 375, 375, 375, 375, 376,
+ 376, 378, 377, 380, 379, 382, 381, 383, 383, 383,
+ 384, 384, 384, 384, 384, 384, 385, 386, 387, 388,
+ 389, 390, 392, 391, 393, 393, 394, 394, 394, 396,
+ 395, 398, 397, 399, 399, 399, 400, 400, 400, 400,
+ 400, 400, 400, 400, 400, 400, 400, 400, 400, 400,
+ 400, 400, 400, 400, 400, 400, 400, 400, 400, 400,
+ 400, 400, 400, 400, 400, 400, 400, 400, 400, 400,
+ 400, 400, 400, 400, 400, 400, 400, 400, 400, 400,
+ 400, 402, 401, 404, 403, 406, 405, 408, 407, 410,
+ 409, 412, 411, 414, 413, 415, 416, 417, 419, 418,
+ 420, 420, 420, 420, 421, 423, 422, 424, 424, 425,
+ 425, 425, 427, 426, 428, 428, 428, 429, 429, 429,
+ 429, 429, 429, 429, 429, 429, 429, 429, 429, 429,
+ 429, 429, 429, 429, 429, 429, 429, 429, 429, 429,
+ 429, 429, 429, 429, 429, 429, 429, 429, 429, 429,
+ 429, 429, 429, 429, 429, 429, 429, 431, 430, 433,
+ 432, 434, 434, 435, 435, 435, 437, 436, 439, 438,
+ 440, 440, 441, 441, 441, 442, 442, 442, 442, 442,
+ 442, 442, 442, 442, 442, 443, 444, 445, 447, 446,
+ 449, 448, 451, 450, 452, 454, 453, 455, 457, 456,
+ 458, 458, 459, 459, 459, 461, 460, 463, 462, 464,
+ 464, 465, 465, 465, 466, 466, 466, 466, 466, 466,
+ 466, 466, 466, 467, 469, 468, 470, 471, 472, 473,
+ 475, 474, 476, 476, 477, 477, 477, 479, 478, 481,
+ 480, 482, 482, 482, 483, 483, 483, 483, 483, 483,
+ 483, 485, 484, 487, 486, 489, 488, 491, 490, 492,
+ 492, 493, 493, 493, 495, 494, 497, 496, 498, 498,
+ 499, 499, 499, 500, 500, 500, 500, 500, 500, 500,
+ 500, 500, 500, 500, 500, 500, 500, 500, 502, 501,
+ 504, 503, 506, 505, 508, 507, 510, 509, 512, 511,
+ 514, 513, 516, 515, 518, 517, 520, 519, 522, 521,
+ 524, 523, 526, 525, 527, 527, 529, 528, 530, 530,
+ 530, 532, 531, 533, 533, 534, 534, 534, 535, 535,
+ 535, 535, 535, 535, 535, 535, 535, 535, 535, 535,
+ 535, 535, 536, 538, 537, 539, 540, 542, 541, 543,
+ 543, 543, 544, 544, 544, 544, 544, 546, 545, 548,
+ 547, 550, 549, 551, 551, 551, 552, 552, 552, 552,
+ 552, 552, 553, 555, 554, 556, 558, 557, 560, 559,
+ 562, 561, 563, 563, 563, 564, 564, 564, 564, 564,
+ 564, 564, 564, 564, 564, 564, 564, 564, 564, 564,
+ 564, 564, 564, 565, 567, 566, 568, 570, 569, 571,
+ 572, 574, 573, 575, 575, 577, 576, 579, 578, 580,
+ 581, 583, 582, 585, 584, 587, 586, 589, 588, 591,
+ 590, 593, 592, 594, 594, 594, 595, 595, 597, 596,
+ 598, 600, 599, 601, 601, 601, 603, 602, 604, 604,
+ 604, 605, 605, 605, 605, 605, 605, 605, 606, 608,
+ 607, 610, 609, 611, 611, 611, 613, 612, 614, 614,
+ 614, 615, 615, 615, 615, 615, 617, 616, 618, 619,
+ 620, 622, 621, 624, 623, 625, 625, 625, 626, 626,
+ 627
+ };
+
+ const signed char
+ Dhcp4Parser::yyr2_[] =
+ {
+ 0, 2, 0, 3, 0, 3, 0, 3, 0, 3,
+ 0, 3, 0, 3, 0, 3, 0, 3, 0, 3,
+ 0, 3, 0, 3, 0, 3, 0, 3, 1, 1,
+ 1, 1, 1, 1, 1, 1, 0, 4, 1, 0,
+ 1, 3, 5, 2, 0, 4, 0, 1, 1, 3,
+ 2, 0, 4, 0, 1, 1, 3, 2, 2, 0,
+ 4, 0, 6, 1, 2, 0, 4, 1, 3, 2,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 0, 4, 3, 3, 3,
+ 3, 3, 3, 3, 0, 4, 1, 1, 1, 1,
+ 1, 0, 4, 0, 4, 3, 3, 0, 4, 0,
+ 4, 3, 3, 3, 3, 3, 3, 0, 6, 1,
+ 3, 2, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 0, 4, 0, 4, 0, 4, 1, 1,
+ 0, 4, 1, 1, 3, 3, 3, 3, 0, 6,
+ 0, 6, 1, 3, 2, 1, 0, 4, 0, 6,
+ 0, 6, 0, 1, 1, 3, 2, 0, 4, 1,
+ 3, 2, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 0, 4, 1, 1, 1, 0, 4, 0, 4,
+ 0, 4, 3, 0, 4, 3, 3, 3, 3, 3,
+ 3, 0, 4, 1, 1, 1, 3, 0, 4, 0,
+ 4, 0, 4, 0, 4, 0, 6, 1, 3, 2,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 6, 1, 3, 2, 1, 1, 1, 1, 1,
+ 1, 3, 3, 3, 0, 6, 0, 1, 1, 3,
+ 2, 0, 4, 0, 4, 1, 3, 2, 1, 1,
+ 1, 0, 4, 0, 4, 0, 6, 1, 3, 2,
+ 1, 1, 1, 1, 1, 1, 3, 3, 3, 3,
+ 3, 3, 0, 6, 0, 1, 1, 3, 2, 0,
+ 4, 0, 4, 1, 3, 2, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 0, 4, 0, 4, 0, 4, 0, 4, 0,
+ 4, 0, 4, 0, 4, 3, 3, 3, 0, 4,
+ 1, 1, 1, 1, 3, 0, 6, 0, 1, 1,
+ 3, 2, 0, 4, 1, 3, 2, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 0, 6, 0,
+ 4, 0, 1, 1, 3, 2, 0, 4, 0, 4,
+ 0, 1, 1, 3, 2, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 3, 1, 0, 4,
+ 0, 4, 0, 4, 1, 0, 4, 3, 0, 6,
+ 0, 1, 1, 3, 2, 0, 4, 0, 4, 0,
+ 1, 1, 3, 2, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 0, 4, 1, 1, 3, 3,
+ 0, 6, 0, 1, 1, 3, 2, 0, 4, 0,
+ 4, 1, 3, 2, 1, 1, 1, 1, 1, 1,
+ 1, 0, 4, 0, 4, 0, 4, 0, 6, 0,
+ 1, 1, 3, 2, 0, 4, 0, 4, 0, 1,
+ 1, 3, 2, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 0, 4,
+ 0, 4, 0, 4, 0, 4, 0, 4, 0, 4,
+ 0, 4, 0, 4, 0, 4, 0, 4, 0, 4,
+ 0, 4, 0, 6, 1, 1, 0, 6, 1, 3,
+ 2, 0, 4, 0, 1, 1, 3, 2, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 0, 4, 3, 3, 0, 6, 1,
+ 3, 2, 1, 1, 1, 1, 1, 0, 4, 0,
+ 4, 0, 6, 1, 3, 2, 1, 1, 1, 1,
+ 1, 1, 3, 0, 4, 3, 0, 4, 0, 6,
+ 0, 4, 1, 3, 2, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 3, 0, 4, 3, 0, 4, 3,
+ 3, 0, 4, 1, 1, 0, 4, 0, 4, 3,
+ 3, 0, 4, 0, 4, 0, 4, 0, 4, 0,
+ 6, 0, 4, 1, 3, 2, 1, 1, 0, 6,
+ 3, 0, 6, 1, 3, 2, 0, 4, 1, 3,
+ 2, 1, 1, 1, 1, 1, 1, 1, 3, 0,
+ 4, 0, 6, 1, 3, 2, 0, 4, 1, 3,
+ 2, 1, 1, 1, 1, 1, 0, 4, 3, 3,
+ 3, 0, 4, 0, 6, 1, 3, 2, 1, 1,
+ 3
+ };
+
+
+#if PARSER4_DEBUG || 1
+ // YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
+ // First, the terminals, then, starting at \a YYNTOKENS, nonterminals.
+ const char*
+ const Dhcp4Parser::yytname_[] =
+ {
+ "\"end of file\"", "error", "\"invalid token\"", "\",\"", "\":\"",
+ "\"[\"", "\"]\"", "\"{\"", "\"}\"", "\"null\"", "\"Dhcp4\"",
+ "\"config-control\"", "\"config-databases\"",
+ "\"config-fetch-wait-time\"", "\"interfaces-config\"", "\"interfaces\"",
+ "\"dhcp-socket-type\"", "\"raw\"", "\"udp\"", "\"outbound-interface\"",
+ "\"same-as-inbound\"", "\"use-routing\"", "\"re-detect\"",
+ "\"service-sockets-require-all\"", "\"service-sockets-retry-wait-time\"",
+ "\"service-sockets-max-retries\"", "\"sanity-checks\"",
+ "\"lease-checks\"", "\"echo-client-id\"", "\"match-client-id\"",
+ "\"authoritative\"", "\"next-server\"", "\"server-hostname\"",
+ "\"boot-file-name\"", "\"lease-database\"", "\"hosts-database\"",
+ "\"hosts-databases\"", "\"type\"", "\"memfile\"", "\"mysql\"",
+ "\"postgresql\"", "\"user\"", "\"password\"", "\"host\"", "\"port\"",
+ "\"persist\"", "\"lfc-interval\"", "\"readonly\"", "\"connect-timeout\"",
+ "\"max-reconnect-tries\"", "\"reconnect-wait-time\"", "\"on-fail\"",
+ "\"stop-retry-exit\"", "\"serve-retry-exit\"",
+ "\"serve-retry-continue\"", "\"max-row-errors\"", "\"trust-anchor\"",
+ "\"cert-file\"", "\"key-file\"", "\"cipher-list\"", "\"valid-lifetime\"",
+ "\"min-valid-lifetime\"", "\"max-valid-lifetime\"", "\"renew-timer\"",
+ "\"rebind-timer\"", "\"calculate-tee-times\"", "\"t1-percent\"",
+ "\"t2-percent\"", "\"cache-threshold\"", "\"cache-max-age\"",
+ "\"decline-probation-period\"", "\"server-tag\"",
+ "\"statistic-default-sample-count\"", "\"statistic-default-sample-age\"",
+ "\"ddns-send-updates\"", "\"ddns-override-no-update\"",
+ "\"ddns-override-client-update\"", "\"ddns-replace-client-name\"",
+ "\"ddns-generated-prefix\"", "\"ddns-qualifying-suffix\"",
+ "\"ddns-update-on-renew\"", "\"ddns-use-conflict-resolution\"",
+ "\"store-extended-info\"", "\"subnet4\"", "\"4o6-interface\"",
+ "\"4o6-interface-id\"", "\"4o6-subnet\"", "\"option-def\"",
+ "\"option-data\"", "\"name\"", "\"data\"", "\"code\"", "\"space\"",
+ "\"csv-format\"", "\"always-send\"", "\"record-types\"",
+ "\"encapsulate\"", "\"array\"", "\"parked-packet-limit\"",
+ "\"shared-networks\"", "\"pools\"", "\"pool\"", "\"user-context\"",
+ "\"comment\"", "\"subnet\"", "\"interface\"", "\"id\"",
+ "\"reservation-mode\"", "\"disabled\"", "\"out-of-pool\"", "\"global\"",
+ "\"all\"", "\"reservations-global\"", "\"reservations-in-subnet\"",
+ "\"reservations-out-of-pool\"", "\"host-reservation-identifiers\"",
+ "\"client-classes\"", "\"require-client-classes\"", "\"test\"",
+ "\"only-if-required\"", "\"client-class\"", "\"reservations\"",
+ "\"duid\"", "\"hw-address\"", "\"circuit-id\"", "\"client-id\"",
+ "\"hostname\"", "\"flex-id\"", "\"relay\"", "\"ip-address\"",
+ "\"ip-addresses\"", "\"hooks-libraries\"", "\"library\"",
+ "\"parameters\"", "\"expired-leases-processing\"",
+ "\"reclaim-timer-wait-time\"", "\"flush-reclaimed-timer-wait-time\"",
+ "\"hold-reclaimed-time\"", "\"max-reclaim-leases\"",
+ "\"max-reclaim-time\"", "\"unwarned-reclaim-cycles\"",
+ "\"dhcp4o6-port\"", "\"multi-threading\"", "\"enable-multi-threading\"",
+ "\"thread-pool-size\"", "\"packet-queue-size\"", "\"control-socket\"",
+ "\"socket-type\"", "\"socket-name\"", "\"dhcp-queue-control\"",
+ "\"enable-queue\"", "\"queue-type\"", "\"capacity\"", "\"dhcp-ddns\"",
+ "\"enable-updates\"", "\"qualifying-suffix\"", "\"server-ip\"",
+ "\"server-port\"", "\"sender-ip\"", "\"sender-port\"",
+ "\"max-queue-size\"", "\"ncr-protocol\"", "\"ncr-format\"",
+ "\"override-no-update\"", "\"override-client-update\"",
+ "\"replace-client-name\"", "\"generated-prefix\"", "\"tcp\"", "\"JSON\"",
+ "\"when-present\"", "\"never\"", "\"always\"", "\"when-not-present\"",
+ "\"hostname-char-set\"", "\"hostname-char-replacement\"",
+ "\"early-global-reservations-lookup\"", "\"ip-reservations-unique\"",
+ "\"reservations-lookup-first\"", "\"loggers\"", "\"output_options\"",
+ "\"output\"", "\"debuglevel\"", "\"severity\"", "\"flush\"",
+ "\"maxsize\"", "\"maxver\"", "\"pattern\"", "\"compatibility\"",
+ "\"lenient-option-parsing\"", "TOPLEVEL_JSON", "TOPLEVEL_DHCP4",
+ "SUB_DHCP4", "SUB_INTERFACES4", "SUB_SUBNET4", "SUB_POOL4",
+ "SUB_RESERVATION", "SUB_OPTION_DEFS", "SUB_OPTION_DEF",
+ "SUB_OPTION_DATA", "SUB_HOOKS_LIBRARY", "SUB_DHCP_DDNS",
+ "SUB_CONFIG_CONTROL", "\"constant string\"", "\"integer\"",
+ "\"floating point\"", "\"boolean\"", "$accept", "start", "$@1", "$@2",
+ "$@3", "$@4", "$@5", "$@6", "$@7", "$@8", "$@9", "$@10", "$@11", "$@12",
+ "$@13", "value", "sub_json", "map2", "$@14", "map_value", "map_content",
+ "not_empty_map", "list_generic", "$@15", "list_content",
+ "not_empty_list", "list_strings", "$@16", "list_strings_content",
+ "not_empty_list_strings", "unknown_map_entry", "syntax_map", "$@17",
+ "global_object", "$@18", "global_object_comma", "sub_dhcp4", "$@19",
+ "global_params", "global_param", "valid_lifetime", "min_valid_lifetime",
+ "max_valid_lifetime", "renew_timer", "rebind_timer",
+ "calculate_tee_times", "t1_percent", "t2_percent", "cache_threshold",
+ "cache_max_age", "decline_probation_period", "server_tag", "$@20",
+ "parked_packet_limit", "echo_client_id", "match_client_id",
+ "authoritative", "ddns_send_updates", "ddns_override_no_update",
+ "ddns_override_client_update", "ddns_replace_client_name", "$@21",
+ "ddns_replace_client_name_value", "ddns_generated_prefix", "$@22",
+ "ddns_qualifying_suffix", "$@23", "ddns_update_on_renew",
+ "ddns_use_conflict_resolution", "hostname_char_set", "$@24",
+ "hostname_char_replacement", "$@25", "store_extended_info",
+ "statistic_default_sample_count", "statistic_default_sample_age",
+ "early_global_reservations_lookup", "ip_reservations_unique",
+ "reservations_lookup_first", "interfaces_config", "$@26",
+ "interfaces_config_params", "interfaces_config_param", "sub_interfaces4",
+ "$@27", "interfaces_list", "$@28", "dhcp_socket_type", "$@29",
+ "socket_type", "outbound_interface", "$@30", "outbound_interface_value",
+ "re_detect", "service_sockets_require_all",
+ "service_sockets_retry_wait_time", "service_sockets_max_retries",
+ "lease_database", "$@31", "sanity_checks", "$@32",
+ "sanity_checks_params", "sanity_checks_param", "lease_checks", "$@33",
+ "hosts_database", "$@34", "hosts_databases", "$@35", "database_list",
+ "not_empty_database_list", "database", "$@36", "database_map_params",
+ "database_map_param", "database_type", "$@37", "db_type", "user", "$@38",
+ "password", "$@39", "host", "$@40", "port", "name", "$@41", "persist",
+ "lfc_interval", "readonly", "connect_timeout", "max_reconnect_tries",
+ "reconnect_wait_time", "on_fail", "$@42", "on_fail_mode",
+ "max_row_errors", "trust_anchor", "$@43", "cert_file", "$@44",
+ "key_file", "$@45", "cipher_list", "$@46",
+ "host_reservation_identifiers", "$@47",
+ "host_reservation_identifiers_list", "host_reservation_identifier",
+ "duid_id", "hw_address_id", "circuit_id", "client_id", "flex_id",
+ "dhcp_multi_threading", "$@48", "multi_threading_params",
+ "multi_threading_param", "enable_multi_threading", "thread_pool_size",
+ "packet_queue_size", "hooks_libraries", "$@49", "hooks_libraries_list",
+ "not_empty_hooks_libraries_list", "hooks_library", "$@50",
+ "sub_hooks_library", "$@51", "hooks_params", "hooks_param", "library",
+ "$@52", "parameters", "$@53", "expired_leases_processing", "$@54",
+ "expired_leases_params", "expired_leases_param",
+ "reclaim_timer_wait_time", "flush_reclaimed_timer_wait_time",
+ "hold_reclaimed_time", "max_reclaim_leases", "max_reclaim_time",
+ "unwarned_reclaim_cycles", "subnet4_list", "$@55",
+ "subnet4_list_content", "not_empty_subnet4_list", "subnet4", "$@56",
+ "sub_subnet4", "$@57", "subnet4_params", "subnet4_param", "subnet",
+ "$@58", "subnet_4o6_interface", "$@59", "subnet_4o6_interface_id",
+ "$@60", "subnet_4o6_subnet", "$@61", "interface", "$@62", "client_class",
+ "$@63", "require_client_classes", "$@64", "reservations_global",
+ "reservations_in_subnet", "reservations_out_of_pool", "reservation_mode",
+ "$@65", "hr_mode", "id", "shared_networks", "$@66",
+ "shared_networks_content", "shared_networks_list", "shared_network",
+ "$@67", "shared_network_params", "shared_network_param",
+ "option_def_list", "$@68", "sub_option_def_list", "$@69",
+ "option_def_list_content", "not_empty_option_def_list",
+ "option_def_entry", "$@70", "sub_option_def", "$@71",
+ "option_def_params", "not_empty_option_def_params", "option_def_param",
+ "option_def_name", "code", "option_def_code", "option_def_type", "$@72",
+ "option_def_record_types", "$@73", "space", "$@74", "option_def_space",
+ "option_def_encapsulate", "$@75", "option_def_array", "option_data_list",
+ "$@76", "option_data_list_content", "not_empty_option_data_list",
+ "option_data_entry", "$@77", "sub_option_data", "$@78",
+ "option_data_params", "not_empty_option_data_params",
+ "option_data_param", "option_data_name", "option_data_data", "$@79",
+ "option_data_code", "option_data_space", "option_data_csv_format",
+ "option_data_always_send", "pools_list", "$@80", "pools_list_content",
+ "not_empty_pools_list", "pool_list_entry", "$@81", "sub_pool4", "$@82",
+ "pool_params", "pool_param", "pool_entry", "$@83", "user_context",
+ "$@84", "comment", "$@85", "reservations", "$@86", "reservations_list",
+ "not_empty_reservations_list", "reservation", "$@87", "sub_reservation",
+ "$@88", "reservation_params", "not_empty_reservation_params",
+ "reservation_param", "next_server", "$@89", "server_hostname", "$@90",
+ "boot_file_name", "$@91", "ip_address", "$@92", "ip_addresses", "$@93",
+ "duid", "$@94", "hw_address", "$@95", "client_id_value", "$@96",
+ "circuit_id_value", "$@97", "flex_id_value", "$@98", "hostname", "$@99",
+ "reservation_client_classes", "$@100", "relay", "$@101", "relay_map",
+ "client_classes", "$@102", "client_classes_list", "client_class_entry",
+ "$@103", "client_class_params", "not_empty_client_class_params",
+ "client_class_param", "client_class_name", "client_class_test", "$@104",
+ "only_if_required", "dhcp4o6_port", "control_socket", "$@105",
+ "control_socket_params", "control_socket_param", "control_socket_type",
+ "$@106", "control_socket_name", "$@107", "dhcp_queue_control", "$@108",
+ "queue_control_params", "queue_control_param", "enable_queue",
+ "queue_type", "$@109", "capacity", "arbitrary_map_entry", "$@110",
+ "dhcp_ddns", "$@111", "sub_dhcp_ddns", "$@112", "dhcp_ddns_params",
+ "dhcp_ddns_param", "enable_updates", "server_ip", "$@113", "server_port",
+ "sender_ip", "$@114", "sender_port", "max_queue_size", "ncr_protocol",
+ "$@115", "ncr_protocol_value", "ncr_format", "$@116",
+ "dep_qualifying_suffix", "$@117", "dep_override_no_update",
+ "dep_override_client_update", "dep_replace_client_name", "$@118",
+ "dep_generated_prefix", "$@119", "dep_hostname_char_set", "$@120",
+ "dep_hostname_char_replacement", "$@121", "config_control", "$@122",
+ "sub_config_control", "$@123", "config_control_params",
+ "config_control_param", "config_databases", "$@124",
+ "config_fetch_wait_time", "loggers", "$@125", "loggers_entries",
+ "logger_entry", "$@126", "logger_params", "logger_param", "debuglevel",
+ "severity", "$@127", "output_options_list", "$@128",
+ "output_options_list_content", "output_entry", "$@129",
+ "output_params_list", "output_params", "output", "$@130", "flush",
+ "maxsize", "maxver", "pattern", "$@131", "compatibility", "$@132",
+ "compatibility_params", "compatibility_param", "lenient_option_parsing", YY_NULLPTR
+ };
+#endif
+
+
+#if PARSER4_DEBUG
+ const short
+ Dhcp4Parser::yyrline_[] =
+ {
+ 0, 295, 295, 295, 296, 296, 297, 297, 298, 298,
+ 299, 299, 300, 300, 301, 301, 302, 302, 303, 303,
+ 304, 304, 305, 305, 306, 306, 307, 307, 315, 316,
+ 317, 318, 319, 320, 321, 324, 329, 329, 340, 343,
+ 344, 347, 352, 358, 363, 363, 370, 371, 374, 378,
+ 382, 388, 388, 395, 396, 399, 403, 407, 417, 426,
+ 426, 441, 441, 455, 458, 464, 464, 473, 474, 475,
+ 482, 483, 484, 485, 486, 487, 488, 489, 490, 491,
+ 492, 493, 494, 495, 496, 497, 498, 499, 500, 501,
+ 502, 503, 504, 505, 506, 507, 508, 509, 510, 511,
+ 512, 513, 514, 515, 516, 517, 518, 519, 520, 521,
+ 522, 523, 524, 525, 526, 527, 528, 529, 530, 531,
+ 532, 533, 534, 535, 536, 537, 538, 539, 540, 541,
+ 542, 543, 544, 545, 548, 554, 560, 566, 572, 578,
+ 584, 590, 596, 602, 608, 614, 614, 623, 629, 635,
+ 641, 647, 653, 659, 665, 665, 674, 677, 680, 683,
+ 686, 692, 692, 701, 701, 710, 716, 722, 722, 731,
+ 731, 740, 746, 752, 758, 764, 770, 776, 776, 788,
+ 789, 790, 795, 796, 797, 798, 799, 800, 801, 802,
+ 803, 804, 807, 807, 816, 816, 827, 827, 835, 836,
+ 839, 839, 847, 849, 853, 859, 865, 871, 877, 877,
+ 890, 890, 901, 902, 903, 908, 910, 910, 929, 929,
+ 942, 942, 953, 954, 957, 958, 959, 964, 964, 974,
+ 975, 976, 981, 982, 983, 984, 985, 986, 987, 988,
+ 989, 990, 991, 992, 993, 994, 995, 996, 997, 998,
+ 999, 1002, 1002, 1010, 1011, 1012, 1015, 1015, 1024, 1024,
+ 1033, 1033, 1042, 1048, 1048, 1057, 1063, 1069, 1075, 1081,
+ 1087, 1093, 1093, 1101, 1102, 1103, 1106, 1112, 1112, 1121,
+ 1121, 1130, 1130, 1139, 1139, 1148, 1148, 1159, 1160, 1161,
+ 1166, 1167, 1168, 1169, 1170, 1173, 1178, 1183, 1188, 1193,
+ 1200, 1200, 1213, 1214, 1215, 1220, 1221, 1222, 1223, 1224,
+ 1225, 1228, 1234, 1240, 1246, 1246, 1257, 1258, 1261, 1262,
+ 1263, 1268, 1268, 1278, 1278, 1288, 1289, 1290, 1293, 1296,
+ 1297, 1300, 1300, 1309, 1309, 1318, 1318, 1330, 1331, 1332,
+ 1337, 1338, 1339, 1340, 1341, 1342, 1345, 1351, 1357, 1363,
+ 1369, 1375, 1384, 1384, 1398, 1399, 1402, 1403, 1404, 1413,
+ 1413, 1439, 1439, 1450, 1451, 1452, 1458, 1459, 1460, 1461,
+ 1462, 1463, 1464, 1465, 1466, 1467, 1468, 1469, 1470, 1471,
+ 1472, 1473, 1474, 1475, 1476, 1477, 1478, 1479, 1480, 1481,
+ 1482, 1483, 1484, 1485, 1486, 1487, 1488, 1489, 1490, 1491,
+ 1492, 1493, 1494, 1495, 1496, 1497, 1498, 1499, 1500, 1501,
+ 1502, 1505, 1505, 1514, 1514, 1523, 1523, 1532, 1532, 1541,
+ 1541, 1550, 1550, 1559, 1559, 1570, 1576, 1582, 1588, 1588,
+ 1596, 1597, 1598, 1599, 1602, 1610, 1610, 1622, 1623, 1627,
+ 1628, 1629, 1634, 1634, 1642, 1643, 1644, 1649, 1650, 1651,
+ 1652, 1653, 1654, 1655, 1656, 1657, 1658, 1659, 1660, 1661,
+ 1662, 1663, 1664, 1665, 1666, 1667, 1668, 1669, 1670, 1671,
+ 1672, 1673, 1674, 1675, 1676, 1677, 1678, 1679, 1680, 1681,
+ 1682, 1683, 1684, 1685, 1686, 1687, 1688, 1695, 1695, 1709,
+ 1709, 1718, 1719, 1722, 1723, 1724, 1731, 1731, 1746, 1746,
+ 1760, 1761, 1764, 1765, 1766, 1771, 1772, 1773, 1774, 1775,
+ 1776, 1777, 1778, 1779, 1780, 1783, 1785, 1791, 1793, 1793,
+ 1802, 1802, 1811, 1811, 1820, 1822, 1822, 1831, 1841, 1841,
+ 1854, 1855, 1860, 1861, 1862, 1869, 1869, 1881, 1881, 1893,
+ 1894, 1899, 1900, 1901, 1908, 1909, 1910, 1911, 1912, 1913,
+ 1914, 1915, 1916, 1919, 1921, 1921, 1930, 1932, 1934, 1940,
+ 1949, 1949, 1962, 1963, 1966, 1967, 1968, 1973, 1973, 1983,
+ 1983, 1993, 1994, 1995, 2000, 2001, 2002, 2003, 2004, 2005,
+ 2006, 2009, 2009, 2018, 2018, 2043, 2043, 2073, 2073, 2084,
+ 2085, 2088, 2089, 2090, 2095, 2095, 2104, 2104, 2113, 2114,
+ 2117, 2118, 2119, 2125, 2126, 2127, 2128, 2129, 2130, 2131,
+ 2132, 2133, 2134, 2135, 2136, 2137, 2138, 2139, 2142, 2142,
+ 2151, 2151, 2160, 2160, 2169, 2169, 2178, 2178, 2189, 2189,
+ 2198, 2198, 2207, 2207, 2216, 2216, 2225, 2225, 2234, 2234,
+ 2243, 2243, 2257, 2257, 2268, 2269, 2275, 2275, 2286, 2287,
+ 2288, 2293, 2293, 2303, 2304, 2307, 2308, 2309, 2314, 2315,
+ 2316, 2317, 2318, 2319, 2320, 2321, 2322, 2323, 2324, 2325,
+ 2326, 2327, 2330, 2332, 2332, 2341, 2349, 2357, 2357, 2368,
+ 2369, 2370, 2375, 2376, 2377, 2378, 2379, 2382, 2382, 2391,
+ 2391, 2403, 2403, 2416, 2417, 2418, 2423, 2424, 2425, 2426,
+ 2427, 2428, 2431, 2437, 2437, 2446, 2452, 2452, 2462, 2462,
+ 2475, 2475, 2485, 2486, 2487, 2492, 2493, 2494, 2495, 2496,
+ 2497, 2498, 2499, 2500, 2501, 2502, 2503, 2504, 2505, 2506,
+ 2507, 2508, 2509, 2512, 2518, 2518, 2527, 2533, 2533, 2542,
+ 2548, 2554, 2554, 2563, 2564, 2567, 2567, 2577, 2577, 2587,
+ 2594, 2601, 2601, 2610, 2610, 2620, 2620, 2630, 2630, 2642,
+ 2642, 2654, 2654, 2664, 2665, 2666, 2672, 2673, 2676, 2676,
+ 2687, 2695, 2695, 2708, 2709, 2710, 2716, 2716, 2724, 2725,
+ 2726, 2731, 2732, 2733, 2734, 2735, 2736, 2737, 2740, 2746,
+ 2746, 2755, 2755, 2766, 2767, 2768, 2773, 2773, 2781, 2782,
+ 2783, 2788, 2789, 2790, 2791, 2792, 2795, 2795, 2804, 2810,
+ 2816, 2822, 2822, 2831, 2831, 2842, 2843, 2844, 2849, 2850,
+ 2853
+ };
+
+ void
+ Dhcp4Parser::yy_stack_print_ () const
+ {
+ *yycdebug_ << "Stack now";
+ for (stack_type::const_iterator
+ i = yystack_.begin (),
+ i_end = yystack_.end ();
+ i != i_end; ++i)
+ *yycdebug_ << ' ' << int (i->state);
+ *yycdebug_ << '\n';
+ }
+
+ void
+ Dhcp4Parser::yy_reduce_print_ (int yyrule) const
+ {
+ int yylno = yyrline_[yyrule];
+ int yynrhs = yyr2_[yyrule];
+ // Print the symbols being reduced, and their result.
+ *yycdebug_ << "Reducing stack by rule " << yyrule - 1
+ << " (line " << yylno << "):\n";
+ // The symbols being reduced.
+ for (int yyi = 0; yyi < yynrhs; yyi++)
+ YY_SYMBOL_PRINT (" $" << yyi + 1 << " =",
+ yystack_[(yynrhs) - (yyi + 1)]);
+ }
+#endif // PARSER4_DEBUG
+
+
+#line 14 "dhcp4_parser.yy"
+} } // isc::dhcp
+#line 6142 "dhcp4_parser.cc"
+
+#line 2859 "dhcp4_parser.yy"
+
+
+void
+isc::dhcp::Dhcp4Parser::error(const location_type& loc,
+ const std::string& what)
+{
+ ctx.error(loc, what);
+}
diff --git a/src/bin/dhcp4/dhcp4_parser.h b/src/bin/dhcp4/dhcp4_parser.h
new file mode 100644
index 0000000..86587e3
--- /dev/null
+++ b/src/bin/dhcp4/dhcp4_parser.h
@@ -0,0 +1,5425 @@
+// A Bison parser, made by GNU Bison 3.8.2.
+
+// Skeleton interface for Bison LALR(1) parsers in C++
+
+// Copyright (C) 2002-2015, 2018-2021 Free Software Foundation, Inc.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// As a special exception, you may create a larger work that contains
+// part or all of the Bison parser skeleton and distribute that work
+// under terms of your choice, so long as that work isn't itself a
+// parser generator using the skeleton or a modified version thereof
+// as a parser skeleton. Alternatively, if you modify or redistribute
+// the parser skeleton itself, you may (at your option) remove this
+// special exception, which will cause the skeleton and the resulting
+// Bison output files to be licensed under the GNU General Public
+// License without this special exception.
+
+// This special exception was added by the Free Software Foundation in
+// version 2.2 of Bison.
+
+
+/**
+ ** \file dhcp4_parser.h
+ ** Define the isc::dhcp::parser class.
+ */
+
+// C++ LALR(1) parser skeleton written by Akim Demaille.
+
+// DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+// especially those whose name start with YY_ or yy_. They are
+// private implementation details that can be changed or removed.
+
+#ifndef YY_PARSER4_DHCP4_PARSER_H_INCLUDED
+# define YY_PARSER4_DHCP4_PARSER_H_INCLUDED
+// "%code requires" blocks.
+#line 17 "dhcp4_parser.yy"
+
+#include <string>
+#include <cc/data.h>
+#include <dhcp/option.h>
+#include <boost/lexical_cast.hpp>
+#include <dhcp4/parser_context_decl.h>
+
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace std;
+
+#line 61 "dhcp4_parser.h"
+
+# include <cassert>
+# include <cstdlib> // std::abort
+# include <iostream>
+# include <stdexcept>
+# include <string>
+# include <vector>
+
+#if defined __cplusplus
+# define YY_CPLUSPLUS __cplusplus
+#else
+# define YY_CPLUSPLUS 199711L
+#endif
+
+// Support move semantics when possible.
+#if 201103L <= YY_CPLUSPLUS
+# define YY_MOVE std::move
+# define YY_MOVE_OR_COPY move
+# define YY_MOVE_REF(Type) Type&&
+# define YY_RVREF(Type) Type&&
+# define YY_COPY(Type) Type
+#else
+# define YY_MOVE
+# define YY_MOVE_OR_COPY copy
+# define YY_MOVE_REF(Type) Type&
+# define YY_RVREF(Type) const Type&
+# define YY_COPY(Type) const Type&
+#endif
+
+// Support noexcept when possible.
+#if 201103L <= YY_CPLUSPLUS
+# define YY_NOEXCEPT noexcept
+# define YY_NOTHROW
+#else
+# define YY_NOEXCEPT
+# define YY_NOTHROW throw ()
+#endif
+
+// Support constexpr when possible.
+#if 201703 <= YY_CPLUSPLUS
+# define YY_CONSTEXPR constexpr
+#else
+# define YY_CONSTEXPR
+#endif
+# include "location.hh"
+#include <typeinfo>
+#ifndef PARSER4__ASSERT
+# include <cassert>
+# define PARSER4__ASSERT assert
+#endif
+
+
+#ifndef YY_ATTRIBUTE_PURE
+# if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_PURE __attribute__ ((__pure__))
+# else
+# define YY_ATTRIBUTE_PURE
+# endif
+#endif
+
+#ifndef YY_ATTRIBUTE_UNUSED
+# if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+# else
+# define YY_ATTRIBUTE_UNUSED
+# endif
+#endif
+
+/* Suppress unused-variable warnings by "using" E. */
+#if ! defined lint || defined __GNUC__
+# define YY_USE(E) ((void) (E))
+#else
+# define YY_USE(E) /* empty */
+#endif
+
+/* Suppress an incorrect diagnostic about yylval being uninitialized. */
+#if defined __GNUC__ && ! defined __ICC && 406 <= __GNUC__ * 100 + __GNUC_MINOR__
+# if __GNUC__ * 100 + __GNUC_MINOR__ < 407
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")
+# else
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \
+ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
+# endif
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END \
+ _Pragma ("GCC diagnostic pop")
+#else
+# define YY_INITIAL_VALUE(Value) Value
+#endif
+#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END
+#endif
+#ifndef YY_INITIAL_VALUE
+# define YY_INITIAL_VALUE(Value) /* Nothing. */
+#endif
+
+#if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__
+# define YY_IGNORE_USELESS_CAST_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"")
+# define YY_IGNORE_USELESS_CAST_END \
+ _Pragma ("GCC diagnostic pop")
+#endif
+#ifndef YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_END
+#endif
+
+# ifndef YY_CAST
+# ifdef __cplusplus
+# define YY_CAST(Type, Val) static_cast<Type> (Val)
+# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast<Type> (Val)
+# else
+# define YY_CAST(Type, Val) ((Type) (Val))
+# define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val))
+# endif
+# endif
+# ifndef YY_NULLPTR
+# if defined __cplusplus
+# if 201103L <= __cplusplus
+# define YY_NULLPTR nullptr
+# else
+# define YY_NULLPTR 0
+# endif
+# else
+# define YY_NULLPTR ((void*)0)
+# endif
+# endif
+
+/* Debug traces. */
+#ifndef PARSER4_DEBUG
+# if defined YYDEBUG
+#if YYDEBUG
+# define PARSER4_DEBUG 1
+# else
+# define PARSER4_DEBUG 0
+# endif
+# else /* ! defined YYDEBUG */
+# define PARSER4_DEBUG 1
+# endif /* ! defined YYDEBUG */
+#endif /* ! defined PARSER4_DEBUG */
+
+#line 14 "dhcp4_parser.yy"
+namespace isc { namespace dhcp {
+#line 210 "dhcp4_parser.h"
+
+
+
+
+ /// A Bison parser.
+ class Dhcp4Parser
+ {
+ public:
+#ifdef PARSER4_STYPE
+# ifdef __GNUC__
+# pragma GCC message "bison: do not #define PARSER4_STYPE in C++, use %define api.value.type"
+# endif
+ typedef PARSER4_STYPE value_type;
+#else
+ /// A buffer to store and retrieve objects.
+ ///
+ /// Sort of a variant, but does not keep track of the nature
+ /// of the stored data, since that knowledge is available
+ /// via the current parser state.
+ class value_type
+ {
+ public:
+ /// Type of *this.
+ typedef value_type self_type;
+
+ /// Empty construction.
+ value_type () YY_NOEXCEPT
+ : yyraw_ ()
+ , yytypeid_ (YY_NULLPTR)
+ {}
+
+ /// Construct and fill.
+ template <typename T>
+ value_type (YY_RVREF (T) t)
+ : yytypeid_ (&typeid (T))
+ {
+ PARSER4__ASSERT (sizeof (T) <= size);
+ new (yyas_<T> ()) T (YY_MOVE (t));
+ }
+
+#if 201103L <= YY_CPLUSPLUS
+ /// Non copyable.
+ value_type (const self_type&) = delete;
+ /// Non copyable.
+ self_type& operator= (const self_type&) = delete;
+#endif
+
+ /// Destruction, allowed only if empty.
+ ~value_type () YY_NOEXCEPT
+ {
+ PARSER4__ASSERT (!yytypeid_);
+ }
+
+# if 201103L <= YY_CPLUSPLUS
+ /// Instantiate a \a T in here from \a t.
+ template <typename T, typename... U>
+ T&
+ emplace (U&&... u)
+ {
+ PARSER4__ASSERT (!yytypeid_);
+ PARSER4__ASSERT (sizeof (T) <= size);
+ yytypeid_ = & typeid (T);
+ return *new (yyas_<T> ()) T (std::forward <U>(u)...);
+ }
+# else
+ /// Instantiate an empty \a T in here.
+ template <typename T>
+ T&
+ emplace ()
+ {
+ PARSER4__ASSERT (!yytypeid_);
+ PARSER4__ASSERT (sizeof (T) <= size);
+ yytypeid_ = & typeid (T);
+ return *new (yyas_<T> ()) T ();
+ }
+
+ /// Instantiate a \a T in here from \a t.
+ template <typename T>
+ T&
+ emplace (const T& t)
+ {
+ PARSER4__ASSERT (!yytypeid_);
+ PARSER4__ASSERT (sizeof (T) <= size);
+ yytypeid_ = & typeid (T);
+ return *new (yyas_<T> ()) T (t);
+ }
+# endif
+
+ /// Instantiate an empty \a T in here.
+ /// Obsolete, use emplace.
+ template <typename T>
+ T&
+ build ()
+ {
+ return emplace<T> ();
+ }
+
+ /// Instantiate a \a T in here from \a t.
+ /// Obsolete, use emplace.
+ template <typename T>
+ T&
+ build (const T& t)
+ {
+ return emplace<T> (t);
+ }
+
+ /// Accessor to a built \a T.
+ template <typename T>
+ T&
+ as () YY_NOEXCEPT
+ {
+ PARSER4__ASSERT (yytypeid_);
+ PARSER4__ASSERT (*yytypeid_ == typeid (T));
+ PARSER4__ASSERT (sizeof (T) <= size);
+ return *yyas_<T> ();
+ }
+
+ /// Const accessor to a built \a T (for %printer).
+ template <typename T>
+ const T&
+ as () const YY_NOEXCEPT
+ {
+ PARSER4__ASSERT (yytypeid_);
+ PARSER4__ASSERT (*yytypeid_ == typeid (T));
+ PARSER4__ASSERT (sizeof (T) <= size);
+ return *yyas_<T> ();
+ }
+
+ /// Swap the content with \a that, of same type.
+ ///
+ /// Both variants must be built beforehand, because swapping the actual
+ /// data requires reading it (with as()), and this is not possible on
+ /// unconstructed variants: it would require some dynamic testing, which
+ /// should not be the variant's responsibility.
+ /// Swapping between built and (possibly) non-built is done with
+ /// self_type::move ().
+ template <typename T>
+ void
+ swap (self_type& that) YY_NOEXCEPT
+ {
+ PARSER4__ASSERT (yytypeid_);
+ PARSER4__ASSERT (*yytypeid_ == *that.yytypeid_);
+ std::swap (as<T> (), that.as<T> ());
+ }
+
+ /// Move the content of \a that to this.
+ ///
+ /// Destroys \a that.
+ template <typename T>
+ void
+ move (self_type& that)
+ {
+# if 201103L <= YY_CPLUSPLUS
+ emplace<T> (std::move (that.as<T> ()));
+# else
+ emplace<T> ();
+ swap<T> (that);
+# endif
+ that.destroy<T> ();
+ }
+
+# if 201103L <= YY_CPLUSPLUS
+ /// Move the content of \a that to this.
+ template <typename T>
+ void
+ move (self_type&& that)
+ {
+ emplace<T> (std::move (that.as<T> ()));
+ that.destroy<T> ();
+ }
+#endif
+
+ /// Copy the content of \a that to this.
+ template <typename T>
+ void
+ copy (const self_type& that)
+ {
+ emplace<T> (that.as<T> ());
+ }
+
+ /// Destroy the stored \a T.
+ template <typename T>
+ void
+ destroy ()
+ {
+ as<T> ().~T ();
+ yytypeid_ = YY_NULLPTR;
+ }
+
+ private:
+#if YY_CPLUSPLUS < 201103L
+ /// Non copyable.
+ value_type (const self_type&);
+ /// Non copyable.
+ self_type& operator= (const self_type&);
+#endif
+
+ /// Accessor to raw memory as \a T.
+ template <typename T>
+ T*
+ yyas_ () YY_NOEXCEPT
+ {
+ void *yyp = yyraw_;
+ return static_cast<T*> (yyp);
+ }
+
+ /// Const accessor to raw memory as \a T.
+ template <typename T>
+ const T*
+ yyas_ () const YY_NOEXCEPT
+ {
+ const void *yyp = yyraw_;
+ return static_cast<const T*> (yyp);
+ }
+
+ /// An auxiliary type to compute the largest semantic type.
+ union union_type
+ {
+ // value
+ // map_value
+ // ddns_replace_client_name_value
+ // socket_type
+ // outbound_interface_value
+ // db_type
+ // on_fail_mode
+ // hr_mode
+ // ncr_protocol_value
+ char dummy1[sizeof (ElementPtr)];
+
+ // "boolean"
+ char dummy2[sizeof (bool)];
+
+ // "floating point"
+ char dummy3[sizeof (double)];
+
+ // "integer"
+ char dummy4[sizeof (int64_t)];
+
+ // "constant string"
+ char dummy5[sizeof (std::string)];
+ };
+
+ /// The size of the largest semantic type.
+ enum { size = sizeof (union_type) };
+
+ /// A buffer to store semantic values.
+ union
+ {
+ /// Strongest alignment constraints.
+ long double yyalign_me_;
+ /// A buffer large enough to store any of the semantic values.
+ char yyraw_[size];
+ };
+
+ /// Whether the content is built: if defined, the name of the stored type.
+ const std::type_info *yytypeid_;
+ };
+
+#endif
+ /// Backward compatibility (Bison 3.8).
+ typedef value_type semantic_type;
+
+ /// Symbol locations.
+ typedef location location_type;
+
+ /// Syntax errors thrown from user actions.
+ struct syntax_error : std::runtime_error
+ {
+ syntax_error (const location_type& l, const std::string& m)
+ : std::runtime_error (m)
+ , location (l)
+ {}
+
+ syntax_error (const syntax_error& s)
+ : std::runtime_error (s.what ())
+ , location (s.location)
+ {}
+
+ ~syntax_error () YY_NOEXCEPT YY_NOTHROW;
+
+ location_type location;
+ };
+
+ /// Token kinds.
+ struct token
+ {
+ enum token_kind_type
+ {
+ TOKEN_PARSER4_EMPTY = -2,
+ TOKEN_END = 0, // "end of file"
+ TOKEN_PARSER4_error = 256, // error
+ TOKEN_PARSER4_UNDEF = 257, // "invalid token"
+ TOKEN_COMMA = 258, // ","
+ TOKEN_COLON = 259, // ":"
+ TOKEN_LSQUARE_BRACKET = 260, // "["
+ TOKEN_RSQUARE_BRACKET = 261, // "]"
+ TOKEN_LCURLY_BRACKET = 262, // "{"
+ TOKEN_RCURLY_BRACKET = 263, // "}"
+ TOKEN_NULL_TYPE = 264, // "null"
+ TOKEN_DHCP4 = 265, // "Dhcp4"
+ TOKEN_CONFIG_CONTROL = 266, // "config-control"
+ TOKEN_CONFIG_DATABASES = 267, // "config-databases"
+ TOKEN_CONFIG_FETCH_WAIT_TIME = 268, // "config-fetch-wait-time"
+ TOKEN_INTERFACES_CONFIG = 269, // "interfaces-config"
+ TOKEN_INTERFACES = 270, // "interfaces"
+ TOKEN_DHCP_SOCKET_TYPE = 271, // "dhcp-socket-type"
+ TOKEN_RAW = 272, // "raw"
+ TOKEN_UDP = 273, // "udp"
+ TOKEN_OUTBOUND_INTERFACE = 274, // "outbound-interface"
+ TOKEN_SAME_AS_INBOUND = 275, // "same-as-inbound"
+ TOKEN_USE_ROUTING = 276, // "use-routing"
+ TOKEN_RE_DETECT = 277, // "re-detect"
+ TOKEN_SERVICE_SOCKETS_REQUIRE_ALL = 278, // "service-sockets-require-all"
+ TOKEN_SERVICE_SOCKETS_RETRY_WAIT_TIME = 279, // "service-sockets-retry-wait-time"
+ TOKEN_SERVICE_SOCKETS_MAX_RETRIES = 280, // "service-sockets-max-retries"
+ TOKEN_SANITY_CHECKS = 281, // "sanity-checks"
+ TOKEN_LEASE_CHECKS = 282, // "lease-checks"
+ TOKEN_ECHO_CLIENT_ID = 283, // "echo-client-id"
+ TOKEN_MATCH_CLIENT_ID = 284, // "match-client-id"
+ TOKEN_AUTHORITATIVE = 285, // "authoritative"
+ TOKEN_NEXT_SERVER = 286, // "next-server"
+ TOKEN_SERVER_HOSTNAME = 287, // "server-hostname"
+ TOKEN_BOOT_FILE_NAME = 288, // "boot-file-name"
+ TOKEN_LEASE_DATABASE = 289, // "lease-database"
+ TOKEN_HOSTS_DATABASE = 290, // "hosts-database"
+ TOKEN_HOSTS_DATABASES = 291, // "hosts-databases"
+ TOKEN_TYPE = 292, // "type"
+ TOKEN_MEMFILE = 293, // "memfile"
+ TOKEN_MYSQL = 294, // "mysql"
+ TOKEN_POSTGRESQL = 295, // "postgresql"
+ TOKEN_USER = 296, // "user"
+ TOKEN_PASSWORD = 297, // "password"
+ TOKEN_HOST = 298, // "host"
+ TOKEN_PORT = 299, // "port"
+ TOKEN_PERSIST = 300, // "persist"
+ TOKEN_LFC_INTERVAL = 301, // "lfc-interval"
+ TOKEN_READONLY = 302, // "readonly"
+ TOKEN_CONNECT_TIMEOUT = 303, // "connect-timeout"
+ TOKEN_MAX_RECONNECT_TRIES = 304, // "max-reconnect-tries"
+ TOKEN_RECONNECT_WAIT_TIME = 305, // "reconnect-wait-time"
+ TOKEN_ON_FAIL = 306, // "on-fail"
+ TOKEN_STOP_RETRY_EXIT = 307, // "stop-retry-exit"
+ TOKEN_SERVE_RETRY_EXIT = 308, // "serve-retry-exit"
+ TOKEN_SERVE_RETRY_CONTINUE = 309, // "serve-retry-continue"
+ TOKEN_MAX_ROW_ERRORS = 310, // "max-row-errors"
+ TOKEN_TRUST_ANCHOR = 311, // "trust-anchor"
+ TOKEN_CERT_FILE = 312, // "cert-file"
+ TOKEN_KEY_FILE = 313, // "key-file"
+ TOKEN_CIPHER_LIST = 314, // "cipher-list"
+ TOKEN_VALID_LIFETIME = 315, // "valid-lifetime"
+ TOKEN_MIN_VALID_LIFETIME = 316, // "min-valid-lifetime"
+ TOKEN_MAX_VALID_LIFETIME = 317, // "max-valid-lifetime"
+ TOKEN_RENEW_TIMER = 318, // "renew-timer"
+ TOKEN_REBIND_TIMER = 319, // "rebind-timer"
+ TOKEN_CALCULATE_TEE_TIMES = 320, // "calculate-tee-times"
+ TOKEN_T1_PERCENT = 321, // "t1-percent"
+ TOKEN_T2_PERCENT = 322, // "t2-percent"
+ TOKEN_CACHE_THRESHOLD = 323, // "cache-threshold"
+ TOKEN_CACHE_MAX_AGE = 324, // "cache-max-age"
+ TOKEN_DECLINE_PROBATION_PERIOD = 325, // "decline-probation-period"
+ TOKEN_SERVER_TAG = 326, // "server-tag"
+ TOKEN_STATISTIC_DEFAULT_SAMPLE_COUNT = 327, // "statistic-default-sample-count"
+ TOKEN_STATISTIC_DEFAULT_SAMPLE_AGE = 328, // "statistic-default-sample-age"
+ TOKEN_DDNS_SEND_UPDATES = 329, // "ddns-send-updates"
+ TOKEN_DDNS_OVERRIDE_NO_UPDATE = 330, // "ddns-override-no-update"
+ TOKEN_DDNS_OVERRIDE_CLIENT_UPDATE = 331, // "ddns-override-client-update"
+ TOKEN_DDNS_REPLACE_CLIENT_NAME = 332, // "ddns-replace-client-name"
+ TOKEN_DDNS_GENERATED_PREFIX = 333, // "ddns-generated-prefix"
+ TOKEN_DDNS_QUALIFYING_SUFFIX = 334, // "ddns-qualifying-suffix"
+ TOKEN_DDNS_UPDATE_ON_RENEW = 335, // "ddns-update-on-renew"
+ TOKEN_DDNS_USE_CONFLICT_RESOLUTION = 336, // "ddns-use-conflict-resolution"
+ TOKEN_STORE_EXTENDED_INFO = 337, // "store-extended-info"
+ TOKEN_SUBNET4 = 338, // "subnet4"
+ TOKEN_SUBNET_4O6_INTERFACE = 339, // "4o6-interface"
+ TOKEN_SUBNET_4O6_INTERFACE_ID = 340, // "4o6-interface-id"
+ TOKEN_SUBNET_4O6_SUBNET = 341, // "4o6-subnet"
+ TOKEN_OPTION_DEF = 342, // "option-def"
+ TOKEN_OPTION_DATA = 343, // "option-data"
+ TOKEN_NAME = 344, // "name"
+ TOKEN_DATA = 345, // "data"
+ TOKEN_CODE = 346, // "code"
+ TOKEN_SPACE = 347, // "space"
+ TOKEN_CSV_FORMAT = 348, // "csv-format"
+ TOKEN_ALWAYS_SEND = 349, // "always-send"
+ TOKEN_RECORD_TYPES = 350, // "record-types"
+ TOKEN_ENCAPSULATE = 351, // "encapsulate"
+ TOKEN_ARRAY = 352, // "array"
+ TOKEN_PARKED_PACKET_LIMIT = 353, // "parked-packet-limit"
+ TOKEN_SHARED_NETWORKS = 354, // "shared-networks"
+ TOKEN_POOLS = 355, // "pools"
+ TOKEN_POOL = 356, // "pool"
+ TOKEN_USER_CONTEXT = 357, // "user-context"
+ TOKEN_COMMENT = 358, // "comment"
+ TOKEN_SUBNET = 359, // "subnet"
+ TOKEN_INTERFACE = 360, // "interface"
+ TOKEN_ID = 361, // "id"
+ TOKEN_RESERVATION_MODE = 362, // "reservation-mode"
+ TOKEN_DISABLED = 363, // "disabled"
+ TOKEN_OUT_OF_POOL = 364, // "out-of-pool"
+ TOKEN_GLOBAL = 365, // "global"
+ TOKEN_ALL = 366, // "all"
+ TOKEN_RESERVATIONS_GLOBAL = 367, // "reservations-global"
+ TOKEN_RESERVATIONS_IN_SUBNET = 368, // "reservations-in-subnet"
+ TOKEN_RESERVATIONS_OUT_OF_POOL = 369, // "reservations-out-of-pool"
+ TOKEN_HOST_RESERVATION_IDENTIFIERS = 370, // "host-reservation-identifiers"
+ TOKEN_CLIENT_CLASSES = 371, // "client-classes"
+ TOKEN_REQUIRE_CLIENT_CLASSES = 372, // "require-client-classes"
+ TOKEN_TEST = 373, // "test"
+ TOKEN_ONLY_IF_REQUIRED = 374, // "only-if-required"
+ TOKEN_CLIENT_CLASS = 375, // "client-class"
+ TOKEN_RESERVATIONS = 376, // "reservations"
+ TOKEN_DUID = 377, // "duid"
+ TOKEN_HW_ADDRESS = 378, // "hw-address"
+ TOKEN_CIRCUIT_ID = 379, // "circuit-id"
+ TOKEN_CLIENT_ID = 380, // "client-id"
+ TOKEN_HOSTNAME = 381, // "hostname"
+ TOKEN_FLEX_ID = 382, // "flex-id"
+ TOKEN_RELAY = 383, // "relay"
+ TOKEN_IP_ADDRESS = 384, // "ip-address"
+ TOKEN_IP_ADDRESSES = 385, // "ip-addresses"
+ TOKEN_HOOKS_LIBRARIES = 386, // "hooks-libraries"
+ TOKEN_LIBRARY = 387, // "library"
+ TOKEN_PARAMETERS = 388, // "parameters"
+ TOKEN_EXPIRED_LEASES_PROCESSING = 389, // "expired-leases-processing"
+ TOKEN_RECLAIM_TIMER_WAIT_TIME = 390, // "reclaim-timer-wait-time"
+ TOKEN_FLUSH_RECLAIMED_TIMER_WAIT_TIME = 391, // "flush-reclaimed-timer-wait-time"
+ TOKEN_HOLD_RECLAIMED_TIME = 392, // "hold-reclaimed-time"
+ TOKEN_MAX_RECLAIM_LEASES = 393, // "max-reclaim-leases"
+ TOKEN_MAX_RECLAIM_TIME = 394, // "max-reclaim-time"
+ TOKEN_UNWARNED_RECLAIM_CYCLES = 395, // "unwarned-reclaim-cycles"
+ TOKEN_DHCP4O6_PORT = 396, // "dhcp4o6-port"
+ TOKEN_DHCP_MULTI_THREADING = 397, // "multi-threading"
+ TOKEN_ENABLE_MULTI_THREADING = 398, // "enable-multi-threading"
+ TOKEN_THREAD_POOL_SIZE = 399, // "thread-pool-size"
+ TOKEN_PACKET_QUEUE_SIZE = 400, // "packet-queue-size"
+ TOKEN_CONTROL_SOCKET = 401, // "control-socket"
+ TOKEN_SOCKET_TYPE = 402, // "socket-type"
+ TOKEN_SOCKET_NAME = 403, // "socket-name"
+ TOKEN_DHCP_QUEUE_CONTROL = 404, // "dhcp-queue-control"
+ TOKEN_ENABLE_QUEUE = 405, // "enable-queue"
+ TOKEN_QUEUE_TYPE = 406, // "queue-type"
+ TOKEN_CAPACITY = 407, // "capacity"
+ TOKEN_DHCP_DDNS = 408, // "dhcp-ddns"
+ TOKEN_ENABLE_UPDATES = 409, // "enable-updates"
+ TOKEN_QUALIFYING_SUFFIX = 410, // "qualifying-suffix"
+ TOKEN_SERVER_IP = 411, // "server-ip"
+ TOKEN_SERVER_PORT = 412, // "server-port"
+ TOKEN_SENDER_IP = 413, // "sender-ip"
+ TOKEN_SENDER_PORT = 414, // "sender-port"
+ TOKEN_MAX_QUEUE_SIZE = 415, // "max-queue-size"
+ TOKEN_NCR_PROTOCOL = 416, // "ncr-protocol"
+ TOKEN_NCR_FORMAT = 417, // "ncr-format"
+ TOKEN_OVERRIDE_NO_UPDATE = 418, // "override-no-update"
+ TOKEN_OVERRIDE_CLIENT_UPDATE = 419, // "override-client-update"
+ TOKEN_REPLACE_CLIENT_NAME = 420, // "replace-client-name"
+ TOKEN_GENERATED_PREFIX = 421, // "generated-prefix"
+ TOKEN_TCP = 422, // "tcp"
+ TOKEN_JSON = 423, // "JSON"
+ TOKEN_WHEN_PRESENT = 424, // "when-present"
+ TOKEN_NEVER = 425, // "never"
+ TOKEN_ALWAYS = 426, // "always"
+ TOKEN_WHEN_NOT_PRESENT = 427, // "when-not-present"
+ TOKEN_HOSTNAME_CHAR_SET = 428, // "hostname-char-set"
+ TOKEN_HOSTNAME_CHAR_REPLACEMENT = 429, // "hostname-char-replacement"
+ TOKEN_EARLY_GLOBAL_RESERVATIONS_LOOKUP = 430, // "early-global-reservations-lookup"
+ TOKEN_IP_RESERVATIONS_UNIQUE = 431, // "ip-reservations-unique"
+ TOKEN_RESERVATIONS_LOOKUP_FIRST = 432, // "reservations-lookup-first"
+ TOKEN_LOGGERS = 433, // "loggers"
+ TOKEN_OUTPUT_OPTIONS = 434, // "output_options"
+ TOKEN_OUTPUT = 435, // "output"
+ TOKEN_DEBUGLEVEL = 436, // "debuglevel"
+ TOKEN_SEVERITY = 437, // "severity"
+ TOKEN_FLUSH = 438, // "flush"
+ TOKEN_MAXSIZE = 439, // "maxsize"
+ TOKEN_MAXVER = 440, // "maxver"
+ TOKEN_PATTERN = 441, // "pattern"
+ TOKEN_COMPATIBILITY = 442, // "compatibility"
+ TOKEN_LENIENT_OPTION_PARSING = 443, // "lenient-option-parsing"
+ TOKEN_TOPLEVEL_JSON = 444, // TOPLEVEL_JSON
+ TOKEN_TOPLEVEL_DHCP4 = 445, // TOPLEVEL_DHCP4
+ TOKEN_SUB_DHCP4 = 446, // SUB_DHCP4
+ TOKEN_SUB_INTERFACES4 = 447, // SUB_INTERFACES4
+ TOKEN_SUB_SUBNET4 = 448, // SUB_SUBNET4
+ TOKEN_SUB_POOL4 = 449, // SUB_POOL4
+ TOKEN_SUB_RESERVATION = 450, // SUB_RESERVATION
+ TOKEN_SUB_OPTION_DEFS = 451, // SUB_OPTION_DEFS
+ TOKEN_SUB_OPTION_DEF = 452, // SUB_OPTION_DEF
+ TOKEN_SUB_OPTION_DATA = 453, // SUB_OPTION_DATA
+ TOKEN_SUB_HOOKS_LIBRARY = 454, // SUB_HOOKS_LIBRARY
+ TOKEN_SUB_DHCP_DDNS = 455, // SUB_DHCP_DDNS
+ TOKEN_SUB_CONFIG_CONTROL = 456, // SUB_CONFIG_CONTROL
+ TOKEN_STRING = 457, // "constant string"
+ TOKEN_INTEGER = 458, // "integer"
+ TOKEN_FLOAT = 459, // "floating point"
+ TOKEN_BOOLEAN = 460 // "boolean"
+ };
+ /// Backward compatibility alias (Bison 3.6).
+ typedef token_kind_type yytokentype;
+ };
+
+ /// Token kind, as returned by yylex.
+ typedef token::token_kind_type token_kind_type;
+
+ /// Backward compatibility alias (Bison 3.6).
+ typedef token_kind_type token_type;
+
+ /// Symbol kinds.
+ struct symbol_kind
+ {
+ enum symbol_kind_type
+ {
+ YYNTOKENS = 206, ///< Number of tokens.
+ S_YYEMPTY = -2,
+ S_YYEOF = 0, // "end of file"
+ S_YYerror = 1, // error
+ S_YYUNDEF = 2, // "invalid token"
+ S_COMMA = 3, // ","
+ S_COLON = 4, // ":"
+ S_LSQUARE_BRACKET = 5, // "["
+ S_RSQUARE_BRACKET = 6, // "]"
+ S_LCURLY_BRACKET = 7, // "{"
+ S_RCURLY_BRACKET = 8, // "}"
+ S_NULL_TYPE = 9, // "null"
+ S_DHCP4 = 10, // "Dhcp4"
+ S_CONFIG_CONTROL = 11, // "config-control"
+ S_CONFIG_DATABASES = 12, // "config-databases"
+ S_CONFIG_FETCH_WAIT_TIME = 13, // "config-fetch-wait-time"
+ S_INTERFACES_CONFIG = 14, // "interfaces-config"
+ S_INTERFACES = 15, // "interfaces"
+ S_DHCP_SOCKET_TYPE = 16, // "dhcp-socket-type"
+ S_RAW = 17, // "raw"
+ S_UDP = 18, // "udp"
+ S_OUTBOUND_INTERFACE = 19, // "outbound-interface"
+ S_SAME_AS_INBOUND = 20, // "same-as-inbound"
+ S_USE_ROUTING = 21, // "use-routing"
+ S_RE_DETECT = 22, // "re-detect"
+ S_SERVICE_SOCKETS_REQUIRE_ALL = 23, // "service-sockets-require-all"
+ S_SERVICE_SOCKETS_RETRY_WAIT_TIME = 24, // "service-sockets-retry-wait-time"
+ S_SERVICE_SOCKETS_MAX_RETRIES = 25, // "service-sockets-max-retries"
+ S_SANITY_CHECKS = 26, // "sanity-checks"
+ S_LEASE_CHECKS = 27, // "lease-checks"
+ S_ECHO_CLIENT_ID = 28, // "echo-client-id"
+ S_MATCH_CLIENT_ID = 29, // "match-client-id"
+ S_AUTHORITATIVE = 30, // "authoritative"
+ S_NEXT_SERVER = 31, // "next-server"
+ S_SERVER_HOSTNAME = 32, // "server-hostname"
+ S_BOOT_FILE_NAME = 33, // "boot-file-name"
+ S_LEASE_DATABASE = 34, // "lease-database"
+ S_HOSTS_DATABASE = 35, // "hosts-database"
+ S_HOSTS_DATABASES = 36, // "hosts-databases"
+ S_TYPE = 37, // "type"
+ S_MEMFILE = 38, // "memfile"
+ S_MYSQL = 39, // "mysql"
+ S_POSTGRESQL = 40, // "postgresql"
+ S_USER = 41, // "user"
+ S_PASSWORD = 42, // "password"
+ S_HOST = 43, // "host"
+ S_PORT = 44, // "port"
+ S_PERSIST = 45, // "persist"
+ S_LFC_INTERVAL = 46, // "lfc-interval"
+ S_READONLY = 47, // "readonly"
+ S_CONNECT_TIMEOUT = 48, // "connect-timeout"
+ S_MAX_RECONNECT_TRIES = 49, // "max-reconnect-tries"
+ S_RECONNECT_WAIT_TIME = 50, // "reconnect-wait-time"
+ S_ON_FAIL = 51, // "on-fail"
+ S_STOP_RETRY_EXIT = 52, // "stop-retry-exit"
+ S_SERVE_RETRY_EXIT = 53, // "serve-retry-exit"
+ S_SERVE_RETRY_CONTINUE = 54, // "serve-retry-continue"
+ S_MAX_ROW_ERRORS = 55, // "max-row-errors"
+ S_TRUST_ANCHOR = 56, // "trust-anchor"
+ S_CERT_FILE = 57, // "cert-file"
+ S_KEY_FILE = 58, // "key-file"
+ S_CIPHER_LIST = 59, // "cipher-list"
+ S_VALID_LIFETIME = 60, // "valid-lifetime"
+ S_MIN_VALID_LIFETIME = 61, // "min-valid-lifetime"
+ S_MAX_VALID_LIFETIME = 62, // "max-valid-lifetime"
+ S_RENEW_TIMER = 63, // "renew-timer"
+ S_REBIND_TIMER = 64, // "rebind-timer"
+ S_CALCULATE_TEE_TIMES = 65, // "calculate-tee-times"
+ S_T1_PERCENT = 66, // "t1-percent"
+ S_T2_PERCENT = 67, // "t2-percent"
+ S_CACHE_THRESHOLD = 68, // "cache-threshold"
+ S_CACHE_MAX_AGE = 69, // "cache-max-age"
+ S_DECLINE_PROBATION_PERIOD = 70, // "decline-probation-period"
+ S_SERVER_TAG = 71, // "server-tag"
+ S_STATISTIC_DEFAULT_SAMPLE_COUNT = 72, // "statistic-default-sample-count"
+ S_STATISTIC_DEFAULT_SAMPLE_AGE = 73, // "statistic-default-sample-age"
+ S_DDNS_SEND_UPDATES = 74, // "ddns-send-updates"
+ S_DDNS_OVERRIDE_NO_UPDATE = 75, // "ddns-override-no-update"
+ S_DDNS_OVERRIDE_CLIENT_UPDATE = 76, // "ddns-override-client-update"
+ S_DDNS_REPLACE_CLIENT_NAME = 77, // "ddns-replace-client-name"
+ S_DDNS_GENERATED_PREFIX = 78, // "ddns-generated-prefix"
+ S_DDNS_QUALIFYING_SUFFIX = 79, // "ddns-qualifying-suffix"
+ S_DDNS_UPDATE_ON_RENEW = 80, // "ddns-update-on-renew"
+ S_DDNS_USE_CONFLICT_RESOLUTION = 81, // "ddns-use-conflict-resolution"
+ S_STORE_EXTENDED_INFO = 82, // "store-extended-info"
+ S_SUBNET4 = 83, // "subnet4"
+ S_SUBNET_4O6_INTERFACE = 84, // "4o6-interface"
+ S_SUBNET_4O6_INTERFACE_ID = 85, // "4o6-interface-id"
+ S_SUBNET_4O6_SUBNET = 86, // "4o6-subnet"
+ S_OPTION_DEF = 87, // "option-def"
+ S_OPTION_DATA = 88, // "option-data"
+ S_NAME = 89, // "name"
+ S_DATA = 90, // "data"
+ S_CODE = 91, // "code"
+ S_SPACE = 92, // "space"
+ S_CSV_FORMAT = 93, // "csv-format"
+ S_ALWAYS_SEND = 94, // "always-send"
+ S_RECORD_TYPES = 95, // "record-types"
+ S_ENCAPSULATE = 96, // "encapsulate"
+ S_ARRAY = 97, // "array"
+ S_PARKED_PACKET_LIMIT = 98, // "parked-packet-limit"
+ S_SHARED_NETWORKS = 99, // "shared-networks"
+ S_POOLS = 100, // "pools"
+ S_POOL = 101, // "pool"
+ S_USER_CONTEXT = 102, // "user-context"
+ S_COMMENT = 103, // "comment"
+ S_SUBNET = 104, // "subnet"
+ S_INTERFACE = 105, // "interface"
+ S_ID = 106, // "id"
+ S_RESERVATION_MODE = 107, // "reservation-mode"
+ S_DISABLED = 108, // "disabled"
+ S_OUT_OF_POOL = 109, // "out-of-pool"
+ S_GLOBAL = 110, // "global"
+ S_ALL = 111, // "all"
+ S_RESERVATIONS_GLOBAL = 112, // "reservations-global"
+ S_RESERVATIONS_IN_SUBNET = 113, // "reservations-in-subnet"
+ S_RESERVATIONS_OUT_OF_POOL = 114, // "reservations-out-of-pool"
+ S_HOST_RESERVATION_IDENTIFIERS = 115, // "host-reservation-identifiers"
+ S_CLIENT_CLASSES = 116, // "client-classes"
+ S_REQUIRE_CLIENT_CLASSES = 117, // "require-client-classes"
+ S_TEST = 118, // "test"
+ S_ONLY_IF_REQUIRED = 119, // "only-if-required"
+ S_CLIENT_CLASS = 120, // "client-class"
+ S_RESERVATIONS = 121, // "reservations"
+ S_DUID = 122, // "duid"
+ S_HW_ADDRESS = 123, // "hw-address"
+ S_CIRCUIT_ID = 124, // "circuit-id"
+ S_CLIENT_ID = 125, // "client-id"
+ S_HOSTNAME = 126, // "hostname"
+ S_FLEX_ID = 127, // "flex-id"
+ S_RELAY = 128, // "relay"
+ S_IP_ADDRESS = 129, // "ip-address"
+ S_IP_ADDRESSES = 130, // "ip-addresses"
+ S_HOOKS_LIBRARIES = 131, // "hooks-libraries"
+ S_LIBRARY = 132, // "library"
+ S_PARAMETERS = 133, // "parameters"
+ S_EXPIRED_LEASES_PROCESSING = 134, // "expired-leases-processing"
+ S_RECLAIM_TIMER_WAIT_TIME = 135, // "reclaim-timer-wait-time"
+ S_FLUSH_RECLAIMED_TIMER_WAIT_TIME = 136, // "flush-reclaimed-timer-wait-time"
+ S_HOLD_RECLAIMED_TIME = 137, // "hold-reclaimed-time"
+ S_MAX_RECLAIM_LEASES = 138, // "max-reclaim-leases"
+ S_MAX_RECLAIM_TIME = 139, // "max-reclaim-time"
+ S_UNWARNED_RECLAIM_CYCLES = 140, // "unwarned-reclaim-cycles"
+ S_DHCP4O6_PORT = 141, // "dhcp4o6-port"
+ S_DHCP_MULTI_THREADING = 142, // "multi-threading"
+ S_ENABLE_MULTI_THREADING = 143, // "enable-multi-threading"
+ S_THREAD_POOL_SIZE = 144, // "thread-pool-size"
+ S_PACKET_QUEUE_SIZE = 145, // "packet-queue-size"
+ S_CONTROL_SOCKET = 146, // "control-socket"
+ S_SOCKET_TYPE = 147, // "socket-type"
+ S_SOCKET_NAME = 148, // "socket-name"
+ S_DHCP_QUEUE_CONTROL = 149, // "dhcp-queue-control"
+ S_ENABLE_QUEUE = 150, // "enable-queue"
+ S_QUEUE_TYPE = 151, // "queue-type"
+ S_CAPACITY = 152, // "capacity"
+ S_DHCP_DDNS = 153, // "dhcp-ddns"
+ S_ENABLE_UPDATES = 154, // "enable-updates"
+ S_QUALIFYING_SUFFIX = 155, // "qualifying-suffix"
+ S_SERVER_IP = 156, // "server-ip"
+ S_SERVER_PORT = 157, // "server-port"
+ S_SENDER_IP = 158, // "sender-ip"
+ S_SENDER_PORT = 159, // "sender-port"
+ S_MAX_QUEUE_SIZE = 160, // "max-queue-size"
+ S_NCR_PROTOCOL = 161, // "ncr-protocol"
+ S_NCR_FORMAT = 162, // "ncr-format"
+ S_OVERRIDE_NO_UPDATE = 163, // "override-no-update"
+ S_OVERRIDE_CLIENT_UPDATE = 164, // "override-client-update"
+ S_REPLACE_CLIENT_NAME = 165, // "replace-client-name"
+ S_GENERATED_PREFIX = 166, // "generated-prefix"
+ S_TCP = 167, // "tcp"
+ S_JSON = 168, // "JSON"
+ S_WHEN_PRESENT = 169, // "when-present"
+ S_NEVER = 170, // "never"
+ S_ALWAYS = 171, // "always"
+ S_WHEN_NOT_PRESENT = 172, // "when-not-present"
+ S_HOSTNAME_CHAR_SET = 173, // "hostname-char-set"
+ S_HOSTNAME_CHAR_REPLACEMENT = 174, // "hostname-char-replacement"
+ S_EARLY_GLOBAL_RESERVATIONS_LOOKUP = 175, // "early-global-reservations-lookup"
+ S_IP_RESERVATIONS_UNIQUE = 176, // "ip-reservations-unique"
+ S_RESERVATIONS_LOOKUP_FIRST = 177, // "reservations-lookup-first"
+ S_LOGGERS = 178, // "loggers"
+ S_OUTPUT_OPTIONS = 179, // "output_options"
+ S_OUTPUT = 180, // "output"
+ S_DEBUGLEVEL = 181, // "debuglevel"
+ S_SEVERITY = 182, // "severity"
+ S_FLUSH = 183, // "flush"
+ S_MAXSIZE = 184, // "maxsize"
+ S_MAXVER = 185, // "maxver"
+ S_PATTERN = 186, // "pattern"
+ S_COMPATIBILITY = 187, // "compatibility"
+ S_LENIENT_OPTION_PARSING = 188, // "lenient-option-parsing"
+ S_TOPLEVEL_JSON = 189, // TOPLEVEL_JSON
+ S_TOPLEVEL_DHCP4 = 190, // TOPLEVEL_DHCP4
+ S_SUB_DHCP4 = 191, // SUB_DHCP4
+ S_SUB_INTERFACES4 = 192, // SUB_INTERFACES4
+ S_SUB_SUBNET4 = 193, // SUB_SUBNET4
+ S_SUB_POOL4 = 194, // SUB_POOL4
+ S_SUB_RESERVATION = 195, // SUB_RESERVATION
+ S_SUB_OPTION_DEFS = 196, // SUB_OPTION_DEFS
+ S_SUB_OPTION_DEF = 197, // SUB_OPTION_DEF
+ S_SUB_OPTION_DATA = 198, // SUB_OPTION_DATA
+ S_SUB_HOOKS_LIBRARY = 199, // SUB_HOOKS_LIBRARY
+ S_SUB_DHCP_DDNS = 200, // SUB_DHCP_DDNS
+ S_SUB_CONFIG_CONTROL = 201, // SUB_CONFIG_CONTROL
+ S_STRING = 202, // "constant string"
+ S_INTEGER = 203, // "integer"
+ S_FLOAT = 204, // "floating point"
+ S_BOOLEAN = 205, // "boolean"
+ S_YYACCEPT = 206, // $accept
+ S_start = 207, // start
+ S_208_1 = 208, // $@1
+ S_209_2 = 209, // $@2
+ S_210_3 = 210, // $@3
+ S_211_4 = 211, // $@4
+ S_212_5 = 212, // $@5
+ S_213_6 = 213, // $@6
+ S_214_7 = 214, // $@7
+ S_215_8 = 215, // $@8
+ S_216_9 = 216, // $@9
+ S_217_10 = 217, // $@10
+ S_218_11 = 218, // $@11
+ S_219_12 = 219, // $@12
+ S_220_13 = 220, // $@13
+ S_value = 221, // value
+ S_sub_json = 222, // sub_json
+ S_map2 = 223, // map2
+ S_224_14 = 224, // $@14
+ S_map_value = 225, // map_value
+ S_map_content = 226, // map_content
+ S_not_empty_map = 227, // not_empty_map
+ S_list_generic = 228, // list_generic
+ S_229_15 = 229, // $@15
+ S_list_content = 230, // list_content
+ S_not_empty_list = 231, // not_empty_list
+ S_list_strings = 232, // list_strings
+ S_233_16 = 233, // $@16
+ S_list_strings_content = 234, // list_strings_content
+ S_not_empty_list_strings = 235, // not_empty_list_strings
+ S_unknown_map_entry = 236, // unknown_map_entry
+ S_syntax_map = 237, // syntax_map
+ S_238_17 = 238, // $@17
+ S_global_object = 239, // global_object
+ S_240_18 = 240, // $@18
+ S_global_object_comma = 241, // global_object_comma
+ S_sub_dhcp4 = 242, // sub_dhcp4
+ S_243_19 = 243, // $@19
+ S_global_params = 244, // global_params
+ S_global_param = 245, // global_param
+ S_valid_lifetime = 246, // valid_lifetime
+ S_min_valid_lifetime = 247, // min_valid_lifetime
+ S_max_valid_lifetime = 248, // max_valid_lifetime
+ S_renew_timer = 249, // renew_timer
+ S_rebind_timer = 250, // rebind_timer
+ S_calculate_tee_times = 251, // calculate_tee_times
+ S_t1_percent = 252, // t1_percent
+ S_t2_percent = 253, // t2_percent
+ S_cache_threshold = 254, // cache_threshold
+ S_cache_max_age = 255, // cache_max_age
+ S_decline_probation_period = 256, // decline_probation_period
+ S_server_tag = 257, // server_tag
+ S_258_20 = 258, // $@20
+ S_parked_packet_limit = 259, // parked_packet_limit
+ S_echo_client_id = 260, // echo_client_id
+ S_match_client_id = 261, // match_client_id
+ S_authoritative = 262, // authoritative
+ S_ddns_send_updates = 263, // ddns_send_updates
+ S_ddns_override_no_update = 264, // ddns_override_no_update
+ S_ddns_override_client_update = 265, // ddns_override_client_update
+ S_ddns_replace_client_name = 266, // ddns_replace_client_name
+ S_267_21 = 267, // $@21
+ S_ddns_replace_client_name_value = 268, // ddns_replace_client_name_value
+ S_ddns_generated_prefix = 269, // ddns_generated_prefix
+ S_270_22 = 270, // $@22
+ S_ddns_qualifying_suffix = 271, // ddns_qualifying_suffix
+ S_272_23 = 272, // $@23
+ S_ddns_update_on_renew = 273, // ddns_update_on_renew
+ S_ddns_use_conflict_resolution = 274, // ddns_use_conflict_resolution
+ S_hostname_char_set = 275, // hostname_char_set
+ S_276_24 = 276, // $@24
+ S_hostname_char_replacement = 277, // hostname_char_replacement
+ S_278_25 = 278, // $@25
+ S_store_extended_info = 279, // store_extended_info
+ S_statistic_default_sample_count = 280, // statistic_default_sample_count
+ S_statistic_default_sample_age = 281, // statistic_default_sample_age
+ S_early_global_reservations_lookup = 282, // early_global_reservations_lookup
+ S_ip_reservations_unique = 283, // ip_reservations_unique
+ S_reservations_lookup_first = 284, // reservations_lookup_first
+ S_interfaces_config = 285, // interfaces_config
+ S_286_26 = 286, // $@26
+ S_interfaces_config_params = 287, // interfaces_config_params
+ S_interfaces_config_param = 288, // interfaces_config_param
+ S_sub_interfaces4 = 289, // sub_interfaces4
+ S_290_27 = 290, // $@27
+ S_interfaces_list = 291, // interfaces_list
+ S_292_28 = 292, // $@28
+ S_dhcp_socket_type = 293, // dhcp_socket_type
+ S_294_29 = 294, // $@29
+ S_socket_type = 295, // socket_type
+ S_outbound_interface = 296, // outbound_interface
+ S_297_30 = 297, // $@30
+ S_outbound_interface_value = 298, // outbound_interface_value
+ S_re_detect = 299, // re_detect
+ S_service_sockets_require_all = 300, // service_sockets_require_all
+ S_service_sockets_retry_wait_time = 301, // service_sockets_retry_wait_time
+ S_service_sockets_max_retries = 302, // service_sockets_max_retries
+ S_lease_database = 303, // lease_database
+ S_304_31 = 304, // $@31
+ S_sanity_checks = 305, // sanity_checks
+ S_306_32 = 306, // $@32
+ S_sanity_checks_params = 307, // sanity_checks_params
+ S_sanity_checks_param = 308, // sanity_checks_param
+ S_lease_checks = 309, // lease_checks
+ S_310_33 = 310, // $@33
+ S_hosts_database = 311, // hosts_database
+ S_312_34 = 312, // $@34
+ S_hosts_databases = 313, // hosts_databases
+ S_314_35 = 314, // $@35
+ S_database_list = 315, // database_list
+ S_not_empty_database_list = 316, // not_empty_database_list
+ S_database = 317, // database
+ S_318_36 = 318, // $@36
+ S_database_map_params = 319, // database_map_params
+ S_database_map_param = 320, // database_map_param
+ S_database_type = 321, // database_type
+ S_322_37 = 322, // $@37
+ S_db_type = 323, // db_type
+ S_user = 324, // user
+ S_325_38 = 325, // $@38
+ S_password = 326, // password
+ S_327_39 = 327, // $@39
+ S_host = 328, // host
+ S_329_40 = 329, // $@40
+ S_port = 330, // port
+ S_name = 331, // name
+ S_332_41 = 332, // $@41
+ S_persist = 333, // persist
+ S_lfc_interval = 334, // lfc_interval
+ S_readonly = 335, // readonly
+ S_connect_timeout = 336, // connect_timeout
+ S_max_reconnect_tries = 337, // max_reconnect_tries
+ S_reconnect_wait_time = 338, // reconnect_wait_time
+ S_on_fail = 339, // on_fail
+ S_340_42 = 340, // $@42
+ S_on_fail_mode = 341, // on_fail_mode
+ S_max_row_errors = 342, // max_row_errors
+ S_trust_anchor = 343, // trust_anchor
+ S_344_43 = 344, // $@43
+ S_cert_file = 345, // cert_file
+ S_346_44 = 346, // $@44
+ S_key_file = 347, // key_file
+ S_348_45 = 348, // $@45
+ S_cipher_list = 349, // cipher_list
+ S_350_46 = 350, // $@46
+ S_host_reservation_identifiers = 351, // host_reservation_identifiers
+ S_352_47 = 352, // $@47
+ S_host_reservation_identifiers_list = 353, // host_reservation_identifiers_list
+ S_host_reservation_identifier = 354, // host_reservation_identifier
+ S_duid_id = 355, // duid_id
+ S_hw_address_id = 356, // hw_address_id
+ S_circuit_id = 357, // circuit_id
+ S_client_id = 358, // client_id
+ S_flex_id = 359, // flex_id
+ S_dhcp_multi_threading = 360, // dhcp_multi_threading
+ S_361_48 = 361, // $@48
+ S_multi_threading_params = 362, // multi_threading_params
+ S_multi_threading_param = 363, // multi_threading_param
+ S_enable_multi_threading = 364, // enable_multi_threading
+ S_thread_pool_size = 365, // thread_pool_size
+ S_packet_queue_size = 366, // packet_queue_size
+ S_hooks_libraries = 367, // hooks_libraries
+ S_368_49 = 368, // $@49
+ S_hooks_libraries_list = 369, // hooks_libraries_list
+ S_not_empty_hooks_libraries_list = 370, // not_empty_hooks_libraries_list
+ S_hooks_library = 371, // hooks_library
+ S_372_50 = 372, // $@50
+ S_sub_hooks_library = 373, // sub_hooks_library
+ S_374_51 = 374, // $@51
+ S_hooks_params = 375, // hooks_params
+ S_hooks_param = 376, // hooks_param
+ S_library = 377, // library
+ S_378_52 = 378, // $@52
+ S_parameters = 379, // parameters
+ S_380_53 = 380, // $@53
+ S_expired_leases_processing = 381, // expired_leases_processing
+ S_382_54 = 382, // $@54
+ S_expired_leases_params = 383, // expired_leases_params
+ S_expired_leases_param = 384, // expired_leases_param
+ S_reclaim_timer_wait_time = 385, // reclaim_timer_wait_time
+ S_flush_reclaimed_timer_wait_time = 386, // flush_reclaimed_timer_wait_time
+ S_hold_reclaimed_time = 387, // hold_reclaimed_time
+ S_max_reclaim_leases = 388, // max_reclaim_leases
+ S_max_reclaim_time = 389, // max_reclaim_time
+ S_unwarned_reclaim_cycles = 390, // unwarned_reclaim_cycles
+ S_subnet4_list = 391, // subnet4_list
+ S_392_55 = 392, // $@55
+ S_subnet4_list_content = 393, // subnet4_list_content
+ S_not_empty_subnet4_list = 394, // not_empty_subnet4_list
+ S_subnet4 = 395, // subnet4
+ S_396_56 = 396, // $@56
+ S_sub_subnet4 = 397, // sub_subnet4
+ S_398_57 = 398, // $@57
+ S_subnet4_params = 399, // subnet4_params
+ S_subnet4_param = 400, // subnet4_param
+ S_subnet = 401, // subnet
+ S_402_58 = 402, // $@58
+ S_subnet_4o6_interface = 403, // subnet_4o6_interface
+ S_404_59 = 404, // $@59
+ S_subnet_4o6_interface_id = 405, // subnet_4o6_interface_id
+ S_406_60 = 406, // $@60
+ S_subnet_4o6_subnet = 407, // subnet_4o6_subnet
+ S_408_61 = 408, // $@61
+ S_interface = 409, // interface
+ S_410_62 = 410, // $@62
+ S_client_class = 411, // client_class
+ S_412_63 = 412, // $@63
+ S_require_client_classes = 413, // require_client_classes
+ S_414_64 = 414, // $@64
+ S_reservations_global = 415, // reservations_global
+ S_reservations_in_subnet = 416, // reservations_in_subnet
+ S_reservations_out_of_pool = 417, // reservations_out_of_pool
+ S_reservation_mode = 418, // reservation_mode
+ S_419_65 = 419, // $@65
+ S_hr_mode = 420, // hr_mode
+ S_id = 421, // id
+ S_shared_networks = 422, // shared_networks
+ S_423_66 = 423, // $@66
+ S_shared_networks_content = 424, // shared_networks_content
+ S_shared_networks_list = 425, // shared_networks_list
+ S_shared_network = 426, // shared_network
+ S_427_67 = 427, // $@67
+ S_shared_network_params = 428, // shared_network_params
+ S_shared_network_param = 429, // shared_network_param
+ S_option_def_list = 430, // option_def_list
+ S_431_68 = 431, // $@68
+ S_sub_option_def_list = 432, // sub_option_def_list
+ S_433_69 = 433, // $@69
+ S_option_def_list_content = 434, // option_def_list_content
+ S_not_empty_option_def_list = 435, // not_empty_option_def_list
+ S_option_def_entry = 436, // option_def_entry
+ S_437_70 = 437, // $@70
+ S_sub_option_def = 438, // sub_option_def
+ S_439_71 = 439, // $@71
+ S_option_def_params = 440, // option_def_params
+ S_not_empty_option_def_params = 441, // not_empty_option_def_params
+ S_option_def_param = 442, // option_def_param
+ S_option_def_name = 443, // option_def_name
+ S_code = 444, // code
+ S_option_def_code = 445, // option_def_code
+ S_option_def_type = 446, // option_def_type
+ S_447_72 = 447, // $@72
+ S_option_def_record_types = 448, // option_def_record_types
+ S_449_73 = 449, // $@73
+ S_space = 450, // space
+ S_451_74 = 451, // $@74
+ S_option_def_space = 452, // option_def_space
+ S_option_def_encapsulate = 453, // option_def_encapsulate
+ S_454_75 = 454, // $@75
+ S_option_def_array = 455, // option_def_array
+ S_option_data_list = 456, // option_data_list
+ S_457_76 = 457, // $@76
+ S_option_data_list_content = 458, // option_data_list_content
+ S_not_empty_option_data_list = 459, // not_empty_option_data_list
+ S_option_data_entry = 460, // option_data_entry
+ S_461_77 = 461, // $@77
+ S_sub_option_data = 462, // sub_option_data
+ S_463_78 = 463, // $@78
+ S_option_data_params = 464, // option_data_params
+ S_not_empty_option_data_params = 465, // not_empty_option_data_params
+ S_option_data_param = 466, // option_data_param
+ S_option_data_name = 467, // option_data_name
+ S_option_data_data = 468, // option_data_data
+ S_469_79 = 469, // $@79
+ S_option_data_code = 470, // option_data_code
+ S_option_data_space = 471, // option_data_space
+ S_option_data_csv_format = 472, // option_data_csv_format
+ S_option_data_always_send = 473, // option_data_always_send
+ S_pools_list = 474, // pools_list
+ S_475_80 = 475, // $@80
+ S_pools_list_content = 476, // pools_list_content
+ S_not_empty_pools_list = 477, // not_empty_pools_list
+ S_pool_list_entry = 478, // pool_list_entry
+ S_479_81 = 479, // $@81
+ S_sub_pool4 = 480, // sub_pool4
+ S_481_82 = 481, // $@82
+ S_pool_params = 482, // pool_params
+ S_pool_param = 483, // pool_param
+ S_pool_entry = 484, // pool_entry
+ S_485_83 = 485, // $@83
+ S_user_context = 486, // user_context
+ S_487_84 = 487, // $@84
+ S_comment = 488, // comment
+ S_489_85 = 489, // $@85
+ S_reservations = 490, // reservations
+ S_491_86 = 491, // $@86
+ S_reservations_list = 492, // reservations_list
+ S_not_empty_reservations_list = 493, // not_empty_reservations_list
+ S_reservation = 494, // reservation
+ S_495_87 = 495, // $@87
+ S_sub_reservation = 496, // sub_reservation
+ S_497_88 = 497, // $@88
+ S_reservation_params = 498, // reservation_params
+ S_not_empty_reservation_params = 499, // not_empty_reservation_params
+ S_reservation_param = 500, // reservation_param
+ S_next_server = 501, // next_server
+ S_502_89 = 502, // $@89
+ S_server_hostname = 503, // server_hostname
+ S_504_90 = 504, // $@90
+ S_boot_file_name = 505, // boot_file_name
+ S_506_91 = 506, // $@91
+ S_ip_address = 507, // ip_address
+ S_508_92 = 508, // $@92
+ S_ip_addresses = 509, // ip_addresses
+ S_510_93 = 510, // $@93
+ S_duid = 511, // duid
+ S_512_94 = 512, // $@94
+ S_hw_address = 513, // hw_address
+ S_514_95 = 514, // $@95
+ S_client_id_value = 515, // client_id_value
+ S_516_96 = 516, // $@96
+ S_circuit_id_value = 517, // circuit_id_value
+ S_518_97 = 518, // $@97
+ S_flex_id_value = 519, // flex_id_value
+ S_520_98 = 520, // $@98
+ S_hostname = 521, // hostname
+ S_522_99 = 522, // $@99
+ S_reservation_client_classes = 523, // reservation_client_classes
+ S_524_100 = 524, // $@100
+ S_relay = 525, // relay
+ S_526_101 = 526, // $@101
+ S_relay_map = 527, // relay_map
+ S_client_classes = 528, // client_classes
+ S_529_102 = 529, // $@102
+ S_client_classes_list = 530, // client_classes_list
+ S_client_class_entry = 531, // client_class_entry
+ S_532_103 = 532, // $@103
+ S_client_class_params = 533, // client_class_params
+ S_not_empty_client_class_params = 534, // not_empty_client_class_params
+ S_client_class_param = 535, // client_class_param
+ S_client_class_name = 536, // client_class_name
+ S_client_class_test = 537, // client_class_test
+ S_538_104 = 538, // $@104
+ S_only_if_required = 539, // only_if_required
+ S_dhcp4o6_port = 540, // dhcp4o6_port
+ S_control_socket = 541, // control_socket
+ S_542_105 = 542, // $@105
+ S_control_socket_params = 543, // control_socket_params
+ S_control_socket_param = 544, // control_socket_param
+ S_control_socket_type = 545, // control_socket_type
+ S_546_106 = 546, // $@106
+ S_control_socket_name = 547, // control_socket_name
+ S_548_107 = 548, // $@107
+ S_dhcp_queue_control = 549, // dhcp_queue_control
+ S_550_108 = 550, // $@108
+ S_queue_control_params = 551, // queue_control_params
+ S_queue_control_param = 552, // queue_control_param
+ S_enable_queue = 553, // enable_queue
+ S_queue_type = 554, // queue_type
+ S_555_109 = 555, // $@109
+ S_capacity = 556, // capacity
+ S_arbitrary_map_entry = 557, // arbitrary_map_entry
+ S_558_110 = 558, // $@110
+ S_dhcp_ddns = 559, // dhcp_ddns
+ S_560_111 = 560, // $@111
+ S_sub_dhcp_ddns = 561, // sub_dhcp_ddns
+ S_562_112 = 562, // $@112
+ S_dhcp_ddns_params = 563, // dhcp_ddns_params
+ S_dhcp_ddns_param = 564, // dhcp_ddns_param
+ S_enable_updates = 565, // enable_updates
+ S_server_ip = 566, // server_ip
+ S_567_113 = 567, // $@113
+ S_server_port = 568, // server_port
+ S_sender_ip = 569, // sender_ip
+ S_570_114 = 570, // $@114
+ S_sender_port = 571, // sender_port
+ S_max_queue_size = 572, // max_queue_size
+ S_ncr_protocol = 573, // ncr_protocol
+ S_574_115 = 574, // $@115
+ S_ncr_protocol_value = 575, // ncr_protocol_value
+ S_ncr_format = 576, // ncr_format
+ S_577_116 = 577, // $@116
+ S_dep_qualifying_suffix = 578, // dep_qualifying_suffix
+ S_579_117 = 579, // $@117
+ S_dep_override_no_update = 580, // dep_override_no_update
+ S_dep_override_client_update = 581, // dep_override_client_update
+ S_dep_replace_client_name = 582, // dep_replace_client_name
+ S_583_118 = 583, // $@118
+ S_dep_generated_prefix = 584, // dep_generated_prefix
+ S_585_119 = 585, // $@119
+ S_dep_hostname_char_set = 586, // dep_hostname_char_set
+ S_587_120 = 587, // $@120
+ S_dep_hostname_char_replacement = 588, // dep_hostname_char_replacement
+ S_589_121 = 589, // $@121
+ S_config_control = 590, // config_control
+ S_591_122 = 591, // $@122
+ S_sub_config_control = 592, // sub_config_control
+ S_593_123 = 593, // $@123
+ S_config_control_params = 594, // config_control_params
+ S_config_control_param = 595, // config_control_param
+ S_config_databases = 596, // config_databases
+ S_597_124 = 597, // $@124
+ S_config_fetch_wait_time = 598, // config_fetch_wait_time
+ S_loggers = 599, // loggers
+ S_600_125 = 600, // $@125
+ S_loggers_entries = 601, // loggers_entries
+ S_logger_entry = 602, // logger_entry
+ S_603_126 = 603, // $@126
+ S_logger_params = 604, // logger_params
+ S_logger_param = 605, // logger_param
+ S_debuglevel = 606, // debuglevel
+ S_severity = 607, // severity
+ S_608_127 = 608, // $@127
+ S_output_options_list = 609, // output_options_list
+ S_610_128 = 610, // $@128
+ S_output_options_list_content = 611, // output_options_list_content
+ S_output_entry = 612, // output_entry
+ S_613_129 = 613, // $@129
+ S_output_params_list = 614, // output_params_list
+ S_output_params = 615, // output_params
+ S_output = 616, // output
+ S_617_130 = 617, // $@130
+ S_flush = 618, // flush
+ S_maxsize = 619, // maxsize
+ S_maxver = 620, // maxver
+ S_pattern = 621, // pattern
+ S_622_131 = 622, // $@131
+ S_compatibility = 623, // compatibility
+ S_624_132 = 624, // $@132
+ S_compatibility_params = 625, // compatibility_params
+ S_compatibility_param = 626, // compatibility_param
+ S_lenient_option_parsing = 627 // lenient_option_parsing
+ };
+ };
+
+ /// (Internal) symbol kind.
+ typedef symbol_kind::symbol_kind_type symbol_kind_type;
+
+ /// The number of tokens.
+ static const symbol_kind_type YYNTOKENS = symbol_kind::YYNTOKENS;
+
+ /// A complete symbol.
+ ///
+ /// Expects its Base type to provide access to the symbol kind
+ /// via kind ().
+ ///
+ /// Provide access to semantic value and location.
+ template <typename Base>
+ struct basic_symbol : Base
+ {
+ /// Alias to Base.
+ typedef Base super_type;
+
+ /// Default constructor.
+ basic_symbol () YY_NOEXCEPT
+ : value ()
+ , location ()
+ {}
+
+#if 201103L <= YY_CPLUSPLUS
+ /// Move constructor.
+ basic_symbol (basic_symbol&& that)
+ : Base (std::move (that))
+ , value ()
+ , location (std::move (that.location))
+ {
+ switch (this->kind ())
+ {
+ case symbol_kind::S_value: // value
+ case symbol_kind::S_map_value: // map_value
+ case symbol_kind::S_ddns_replace_client_name_value: // ddns_replace_client_name_value
+ case symbol_kind::S_socket_type: // socket_type
+ case symbol_kind::S_outbound_interface_value: // outbound_interface_value
+ case symbol_kind::S_db_type: // db_type
+ case symbol_kind::S_on_fail_mode: // on_fail_mode
+ case symbol_kind::S_hr_mode: // hr_mode
+ case symbol_kind::S_ncr_protocol_value: // ncr_protocol_value
+ value.move< ElementPtr > (std::move (that.value));
+ break;
+
+ case symbol_kind::S_BOOLEAN: // "boolean"
+ value.move< bool > (std::move (that.value));
+ break;
+
+ case symbol_kind::S_FLOAT: // "floating point"
+ value.move< double > (std::move (that.value));
+ break;
+
+ case symbol_kind::S_INTEGER: // "integer"
+ value.move< int64_t > (std::move (that.value));
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ value.move< std::string > (std::move (that.value));
+ break;
+
+ default:
+ break;
+ }
+
+ }
+#endif
+
+ /// Copy constructor.
+ basic_symbol (const basic_symbol& that);
+
+ /// Constructors for typed symbols.
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, location_type&& l)
+ : Base (t)
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const location_type& l)
+ : Base (t)
+ , location (l)
+ {}
+#endif
+
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, ElementPtr&& v, location_type&& l)
+ : Base (t)
+ , value (std::move (v))
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const ElementPtr& v, const location_type& l)
+ : Base (t)
+ , value (v)
+ , location (l)
+ {}
+#endif
+
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, bool&& v, location_type&& l)
+ : Base (t)
+ , value (std::move (v))
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const bool& v, const location_type& l)
+ : Base (t)
+ , value (v)
+ , location (l)
+ {}
+#endif
+
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, double&& v, location_type&& l)
+ : Base (t)
+ , value (std::move (v))
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const double& v, const location_type& l)
+ : Base (t)
+ , value (v)
+ , location (l)
+ {}
+#endif
+
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, int64_t&& v, location_type&& l)
+ : Base (t)
+ , value (std::move (v))
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const int64_t& v, const location_type& l)
+ : Base (t)
+ , value (v)
+ , location (l)
+ {}
+#endif
+
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, std::string&& v, location_type&& l)
+ : Base (t)
+ , value (std::move (v))
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const std::string& v, const location_type& l)
+ : Base (t)
+ , value (v)
+ , location (l)
+ {}
+#endif
+
+ /// Destroy the symbol.
+ ~basic_symbol ()
+ {
+ clear ();
+ }
+
+
+
+ /// Destroy contents, and record that is empty.
+ void clear () YY_NOEXCEPT
+ {
+ // User destructor.
+ symbol_kind_type yykind = this->kind ();
+ basic_symbol<Base>& yysym = *this;
+ (void) yysym;
+ switch (yykind)
+ {
+ default:
+ break;
+ }
+
+ // Value type destructor.
+switch (yykind)
+ {
+ case symbol_kind::S_value: // value
+ case symbol_kind::S_map_value: // map_value
+ case symbol_kind::S_ddns_replace_client_name_value: // ddns_replace_client_name_value
+ case symbol_kind::S_socket_type: // socket_type
+ case symbol_kind::S_outbound_interface_value: // outbound_interface_value
+ case symbol_kind::S_db_type: // db_type
+ case symbol_kind::S_on_fail_mode: // on_fail_mode
+ case symbol_kind::S_hr_mode: // hr_mode
+ case symbol_kind::S_ncr_protocol_value: // ncr_protocol_value
+ value.template destroy< ElementPtr > ();
+ break;
+
+ case symbol_kind::S_BOOLEAN: // "boolean"
+ value.template destroy< bool > ();
+ break;
+
+ case symbol_kind::S_FLOAT: // "floating point"
+ value.template destroy< double > ();
+ break;
+
+ case symbol_kind::S_INTEGER: // "integer"
+ value.template destroy< int64_t > ();
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ value.template destroy< std::string > ();
+ break;
+
+ default:
+ break;
+ }
+
+ Base::clear ();
+ }
+
+ /// The user-facing name of this symbol.
+ std::string name () const YY_NOEXCEPT
+ {
+ return Dhcp4Parser::symbol_name (this->kind ());
+ }
+
+ /// Backward compatibility (Bison 3.6).
+ symbol_kind_type type_get () const YY_NOEXCEPT;
+
+ /// Whether empty.
+ bool empty () const YY_NOEXCEPT;
+
+ /// Destructive move, \a s is emptied into this.
+ void move (basic_symbol& s);
+
+ /// The semantic value.
+ value_type value;
+
+ /// The location.
+ location_type location;
+
+ private:
+#if YY_CPLUSPLUS < 201103L
+ /// Assignment operator.
+ basic_symbol& operator= (const basic_symbol& that);
+#endif
+ };
+
+ /// Type access provider for token (enum) based symbols.
+ struct by_kind
+ {
+ /// The symbol kind as needed by the constructor.
+ typedef token_kind_type kind_type;
+
+ /// Default constructor.
+ by_kind () YY_NOEXCEPT;
+
+#if 201103L <= YY_CPLUSPLUS
+ /// Move constructor.
+ by_kind (by_kind&& that) YY_NOEXCEPT;
+#endif
+
+ /// Copy constructor.
+ by_kind (const by_kind& that) YY_NOEXCEPT;
+
+ /// Constructor from (external) token numbers.
+ by_kind (kind_type t) YY_NOEXCEPT;
+
+
+
+ /// Record that this symbol is empty.
+ void clear () YY_NOEXCEPT;
+
+ /// Steal the symbol kind from \a that.
+ void move (by_kind& that);
+
+ /// The (internal) type number (corresponding to \a type).
+ /// \a empty when empty.
+ symbol_kind_type kind () const YY_NOEXCEPT;
+
+ /// Backward compatibility (Bison 3.6).
+ symbol_kind_type type_get () const YY_NOEXCEPT;
+
+ /// The symbol kind.
+ /// \a S_YYEMPTY when empty.
+ symbol_kind_type kind_;
+ };
+
+ /// Backward compatibility for a private implementation detail (Bison 3.6).
+ typedef by_kind by_type;
+
+ /// "External" symbols: returned by the scanner.
+ struct symbol_type : basic_symbol<by_kind>
+ {
+ /// Superclass.
+ typedef basic_symbol<by_kind> super_type;
+
+ /// Empty symbol.
+ symbol_type () YY_NOEXCEPT {}
+
+ /// Constructor for valueless symbols, and symbols from each type.
+#if 201103L <= YY_CPLUSPLUS
+ symbol_type (int tok, location_type l)
+ : super_type (token_kind_type (tok), std::move (l))
+#else
+ symbol_type (int tok, const location_type& l)
+ : super_type (token_kind_type (tok), l)
+#endif
+ {
+#if !defined _MSC_VER || defined __clang__
+ PARSER4__ASSERT (tok == token::TOKEN_END
+ || (token::TOKEN_PARSER4_error <= tok && tok <= token::TOKEN_SUB_CONFIG_CONTROL));
+#endif
+ }
+#if 201103L <= YY_CPLUSPLUS
+ symbol_type (int tok, bool v, location_type l)
+ : super_type (token_kind_type (tok), std::move (v), std::move (l))
+#else
+ symbol_type (int tok, const bool& v, const location_type& l)
+ : super_type (token_kind_type (tok), v, l)
+#endif
+ {
+#if !defined _MSC_VER || defined __clang__
+ PARSER4__ASSERT (tok == token::TOKEN_BOOLEAN);
+#endif
+ }
+#if 201103L <= YY_CPLUSPLUS
+ symbol_type (int tok, double v, location_type l)
+ : super_type (token_kind_type (tok), std::move (v), std::move (l))
+#else
+ symbol_type (int tok, const double& v, const location_type& l)
+ : super_type (token_kind_type (tok), v, l)
+#endif
+ {
+#if !defined _MSC_VER || defined __clang__
+ PARSER4__ASSERT (tok == token::TOKEN_FLOAT);
+#endif
+ }
+#if 201103L <= YY_CPLUSPLUS
+ symbol_type (int tok, int64_t v, location_type l)
+ : super_type (token_kind_type (tok), std::move (v), std::move (l))
+#else
+ symbol_type (int tok, const int64_t& v, const location_type& l)
+ : super_type (token_kind_type (tok), v, l)
+#endif
+ {
+#if !defined _MSC_VER || defined __clang__
+ PARSER4__ASSERT (tok == token::TOKEN_INTEGER);
+#endif
+ }
+#if 201103L <= YY_CPLUSPLUS
+ symbol_type (int tok, std::string v, location_type l)
+ : super_type (token_kind_type (tok), std::move (v), std::move (l))
+#else
+ symbol_type (int tok, const std::string& v, const location_type& l)
+ : super_type (token_kind_type (tok), v, l)
+#endif
+ {
+#if !defined _MSC_VER || defined __clang__
+ PARSER4__ASSERT (tok == token::TOKEN_STRING);
+#endif
+ }
+ };
+
+ /// Build a parser object.
+ Dhcp4Parser (isc::dhcp::Parser4Context& ctx_yyarg);
+ virtual ~Dhcp4Parser ();
+
+#if 201103L <= YY_CPLUSPLUS
+ /// Non copyable.
+ Dhcp4Parser (const Dhcp4Parser&) = delete;
+ /// Non copyable.
+ Dhcp4Parser& operator= (const Dhcp4Parser&) = delete;
+#endif
+
+ /// Parse. An alias for parse ().
+ /// \returns 0 iff parsing succeeded.
+ int operator() ();
+
+ /// Parse.
+ /// \returns 0 iff parsing succeeded.
+ virtual int parse ();
+
+#if PARSER4_DEBUG
+ /// The current debugging stream.
+ std::ostream& debug_stream () const YY_ATTRIBUTE_PURE;
+ /// Set the current debugging stream.
+ void set_debug_stream (std::ostream &);
+
+ /// Type for debugging levels.
+ typedef int debug_level_type;
+ /// The current debugging level.
+ debug_level_type debug_level () const YY_ATTRIBUTE_PURE;
+ /// Set the current debugging level.
+ void set_debug_level (debug_level_type l);
+#endif
+
+ /// Report a syntax error.
+ /// \param loc where the syntax error is found.
+ /// \param msg a description of the syntax error.
+ virtual void error (const location_type& loc, const std::string& msg);
+
+ /// Report a syntax error.
+ void error (const syntax_error& err);
+
+ /// The user-facing name of the symbol whose (internal) number is
+ /// YYSYMBOL. No bounds checking.
+ static std::string symbol_name (symbol_kind_type yysymbol);
+
+ // Implementation of make_symbol for each token kind.
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_END (location_type l)
+ {
+ return symbol_type (token::TOKEN_END, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_END (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_END, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_PARSER4_error (location_type l)
+ {
+ return symbol_type (token::TOKEN_PARSER4_error, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_PARSER4_error (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_PARSER4_error, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_PARSER4_UNDEF (location_type l)
+ {
+ return symbol_type (token::TOKEN_PARSER4_UNDEF, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_PARSER4_UNDEF (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_PARSER4_UNDEF, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_COMMA (location_type l)
+ {
+ return symbol_type (token::TOKEN_COMMA, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_COMMA (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_COMMA, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_COLON (location_type l)
+ {
+ return symbol_type (token::TOKEN_COLON, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_COLON (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_COLON, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_LSQUARE_BRACKET (location_type l)
+ {
+ return symbol_type (token::TOKEN_LSQUARE_BRACKET, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_LSQUARE_BRACKET (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_LSQUARE_BRACKET, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_RSQUARE_BRACKET (location_type l)
+ {
+ return symbol_type (token::TOKEN_RSQUARE_BRACKET, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_RSQUARE_BRACKET (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_RSQUARE_BRACKET, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_LCURLY_BRACKET (location_type l)
+ {
+ return symbol_type (token::TOKEN_LCURLY_BRACKET, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_LCURLY_BRACKET (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_LCURLY_BRACKET, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_RCURLY_BRACKET (location_type l)
+ {
+ return symbol_type (token::TOKEN_RCURLY_BRACKET, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_RCURLY_BRACKET (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_RCURLY_BRACKET, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_NULL_TYPE (location_type l)
+ {
+ return symbol_type (token::TOKEN_NULL_TYPE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_NULL_TYPE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_NULL_TYPE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DHCP4 (location_type l)
+ {
+ return symbol_type (token::TOKEN_DHCP4, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DHCP4 (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DHCP4, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_CONFIG_CONTROL (location_type l)
+ {
+ return symbol_type (token::TOKEN_CONFIG_CONTROL, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_CONFIG_CONTROL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_CONFIG_CONTROL, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_CONFIG_DATABASES (location_type l)
+ {
+ return symbol_type (token::TOKEN_CONFIG_DATABASES, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_CONFIG_DATABASES (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_CONFIG_DATABASES, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_CONFIG_FETCH_WAIT_TIME (location_type l)
+ {
+ return symbol_type (token::TOKEN_CONFIG_FETCH_WAIT_TIME, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_CONFIG_FETCH_WAIT_TIME (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_CONFIG_FETCH_WAIT_TIME, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_INTERFACES_CONFIG (location_type l)
+ {
+ return symbol_type (token::TOKEN_INTERFACES_CONFIG, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_INTERFACES_CONFIG (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_INTERFACES_CONFIG, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_INTERFACES (location_type l)
+ {
+ return symbol_type (token::TOKEN_INTERFACES, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_INTERFACES (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_INTERFACES, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DHCP_SOCKET_TYPE (location_type l)
+ {
+ return symbol_type (token::TOKEN_DHCP_SOCKET_TYPE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DHCP_SOCKET_TYPE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DHCP_SOCKET_TYPE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_RAW (location_type l)
+ {
+ return symbol_type (token::TOKEN_RAW, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_RAW (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_RAW, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_UDP (location_type l)
+ {
+ return symbol_type (token::TOKEN_UDP, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_UDP (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_UDP, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_OUTBOUND_INTERFACE (location_type l)
+ {
+ return symbol_type (token::TOKEN_OUTBOUND_INTERFACE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_OUTBOUND_INTERFACE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_OUTBOUND_INTERFACE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SAME_AS_INBOUND (location_type l)
+ {
+ return symbol_type (token::TOKEN_SAME_AS_INBOUND, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SAME_AS_INBOUND (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SAME_AS_INBOUND, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_USE_ROUTING (location_type l)
+ {
+ return symbol_type (token::TOKEN_USE_ROUTING, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_USE_ROUTING (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_USE_ROUTING, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_RE_DETECT (location_type l)
+ {
+ return symbol_type (token::TOKEN_RE_DETECT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_RE_DETECT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_RE_DETECT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SERVICE_SOCKETS_REQUIRE_ALL (location_type l)
+ {
+ return symbol_type (token::TOKEN_SERVICE_SOCKETS_REQUIRE_ALL, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SERVICE_SOCKETS_REQUIRE_ALL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SERVICE_SOCKETS_REQUIRE_ALL, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SERVICE_SOCKETS_RETRY_WAIT_TIME (location_type l)
+ {
+ return symbol_type (token::TOKEN_SERVICE_SOCKETS_RETRY_WAIT_TIME, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SERVICE_SOCKETS_RETRY_WAIT_TIME (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SERVICE_SOCKETS_RETRY_WAIT_TIME, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SERVICE_SOCKETS_MAX_RETRIES (location_type l)
+ {
+ return symbol_type (token::TOKEN_SERVICE_SOCKETS_MAX_RETRIES, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SERVICE_SOCKETS_MAX_RETRIES (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SERVICE_SOCKETS_MAX_RETRIES, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SANITY_CHECKS (location_type l)
+ {
+ return symbol_type (token::TOKEN_SANITY_CHECKS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SANITY_CHECKS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SANITY_CHECKS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_LEASE_CHECKS (location_type l)
+ {
+ return symbol_type (token::TOKEN_LEASE_CHECKS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_LEASE_CHECKS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_LEASE_CHECKS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_ECHO_CLIENT_ID (location_type l)
+ {
+ return symbol_type (token::TOKEN_ECHO_CLIENT_ID, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_ECHO_CLIENT_ID (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_ECHO_CLIENT_ID, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_MATCH_CLIENT_ID (location_type l)
+ {
+ return symbol_type (token::TOKEN_MATCH_CLIENT_ID, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_MATCH_CLIENT_ID (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_MATCH_CLIENT_ID, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_AUTHORITATIVE (location_type l)
+ {
+ return symbol_type (token::TOKEN_AUTHORITATIVE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_AUTHORITATIVE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_AUTHORITATIVE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_NEXT_SERVER (location_type l)
+ {
+ return symbol_type (token::TOKEN_NEXT_SERVER, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_NEXT_SERVER (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_NEXT_SERVER, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SERVER_HOSTNAME (location_type l)
+ {
+ return symbol_type (token::TOKEN_SERVER_HOSTNAME, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SERVER_HOSTNAME (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SERVER_HOSTNAME, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_BOOT_FILE_NAME (location_type l)
+ {
+ return symbol_type (token::TOKEN_BOOT_FILE_NAME, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_BOOT_FILE_NAME (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_BOOT_FILE_NAME, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_LEASE_DATABASE (location_type l)
+ {
+ return symbol_type (token::TOKEN_LEASE_DATABASE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_LEASE_DATABASE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_LEASE_DATABASE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_HOSTS_DATABASE (location_type l)
+ {
+ return symbol_type (token::TOKEN_HOSTS_DATABASE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_HOSTS_DATABASE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_HOSTS_DATABASE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_HOSTS_DATABASES (location_type l)
+ {
+ return symbol_type (token::TOKEN_HOSTS_DATABASES, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_HOSTS_DATABASES (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_HOSTS_DATABASES, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_TYPE (location_type l)
+ {
+ return symbol_type (token::TOKEN_TYPE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_TYPE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_TYPE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_MEMFILE (location_type l)
+ {
+ return symbol_type (token::TOKEN_MEMFILE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_MEMFILE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_MEMFILE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_MYSQL (location_type l)
+ {
+ return symbol_type (token::TOKEN_MYSQL, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_MYSQL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_MYSQL, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_POSTGRESQL (location_type l)
+ {
+ return symbol_type (token::TOKEN_POSTGRESQL, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_POSTGRESQL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_POSTGRESQL, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_USER (location_type l)
+ {
+ return symbol_type (token::TOKEN_USER, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_USER (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_USER, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_PASSWORD (location_type l)
+ {
+ return symbol_type (token::TOKEN_PASSWORD, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_PASSWORD (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_PASSWORD, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_HOST (location_type l)
+ {
+ return symbol_type (token::TOKEN_HOST, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_HOST (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_HOST, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_PORT (location_type l)
+ {
+ return symbol_type (token::TOKEN_PORT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_PORT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_PORT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_PERSIST (location_type l)
+ {
+ return symbol_type (token::TOKEN_PERSIST, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_PERSIST (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_PERSIST, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_LFC_INTERVAL (location_type l)
+ {
+ return symbol_type (token::TOKEN_LFC_INTERVAL, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_LFC_INTERVAL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_LFC_INTERVAL, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_READONLY (location_type l)
+ {
+ return symbol_type (token::TOKEN_READONLY, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_READONLY (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_READONLY, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_CONNECT_TIMEOUT (location_type l)
+ {
+ return symbol_type (token::TOKEN_CONNECT_TIMEOUT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_CONNECT_TIMEOUT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_CONNECT_TIMEOUT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_MAX_RECONNECT_TRIES (location_type l)
+ {
+ return symbol_type (token::TOKEN_MAX_RECONNECT_TRIES, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_MAX_RECONNECT_TRIES (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_MAX_RECONNECT_TRIES, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_RECONNECT_WAIT_TIME (location_type l)
+ {
+ return symbol_type (token::TOKEN_RECONNECT_WAIT_TIME, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_RECONNECT_WAIT_TIME (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_RECONNECT_WAIT_TIME, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_ON_FAIL (location_type l)
+ {
+ return symbol_type (token::TOKEN_ON_FAIL, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_ON_FAIL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_ON_FAIL, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_STOP_RETRY_EXIT (location_type l)
+ {
+ return symbol_type (token::TOKEN_STOP_RETRY_EXIT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_STOP_RETRY_EXIT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_STOP_RETRY_EXIT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SERVE_RETRY_EXIT (location_type l)
+ {
+ return symbol_type (token::TOKEN_SERVE_RETRY_EXIT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SERVE_RETRY_EXIT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SERVE_RETRY_EXIT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SERVE_RETRY_CONTINUE (location_type l)
+ {
+ return symbol_type (token::TOKEN_SERVE_RETRY_CONTINUE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SERVE_RETRY_CONTINUE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SERVE_RETRY_CONTINUE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_MAX_ROW_ERRORS (location_type l)
+ {
+ return symbol_type (token::TOKEN_MAX_ROW_ERRORS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_MAX_ROW_ERRORS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_MAX_ROW_ERRORS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_TRUST_ANCHOR (location_type l)
+ {
+ return symbol_type (token::TOKEN_TRUST_ANCHOR, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_TRUST_ANCHOR (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_TRUST_ANCHOR, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_CERT_FILE (location_type l)
+ {
+ return symbol_type (token::TOKEN_CERT_FILE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_CERT_FILE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_CERT_FILE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_KEY_FILE (location_type l)
+ {
+ return symbol_type (token::TOKEN_KEY_FILE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_KEY_FILE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_KEY_FILE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_CIPHER_LIST (location_type l)
+ {
+ return symbol_type (token::TOKEN_CIPHER_LIST, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_CIPHER_LIST (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_CIPHER_LIST, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_VALID_LIFETIME (location_type l)
+ {
+ return symbol_type (token::TOKEN_VALID_LIFETIME, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_VALID_LIFETIME (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_VALID_LIFETIME, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_MIN_VALID_LIFETIME (location_type l)
+ {
+ return symbol_type (token::TOKEN_MIN_VALID_LIFETIME, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_MIN_VALID_LIFETIME (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_MIN_VALID_LIFETIME, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_MAX_VALID_LIFETIME (location_type l)
+ {
+ return symbol_type (token::TOKEN_MAX_VALID_LIFETIME, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_MAX_VALID_LIFETIME (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_MAX_VALID_LIFETIME, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_RENEW_TIMER (location_type l)
+ {
+ return symbol_type (token::TOKEN_RENEW_TIMER, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_RENEW_TIMER (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_RENEW_TIMER, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_REBIND_TIMER (location_type l)
+ {
+ return symbol_type (token::TOKEN_REBIND_TIMER, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_REBIND_TIMER (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_REBIND_TIMER, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_CALCULATE_TEE_TIMES (location_type l)
+ {
+ return symbol_type (token::TOKEN_CALCULATE_TEE_TIMES, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_CALCULATE_TEE_TIMES (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_CALCULATE_TEE_TIMES, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_T1_PERCENT (location_type l)
+ {
+ return symbol_type (token::TOKEN_T1_PERCENT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_T1_PERCENT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_T1_PERCENT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_T2_PERCENT (location_type l)
+ {
+ return symbol_type (token::TOKEN_T2_PERCENT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_T2_PERCENT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_T2_PERCENT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_CACHE_THRESHOLD (location_type l)
+ {
+ return symbol_type (token::TOKEN_CACHE_THRESHOLD, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_CACHE_THRESHOLD (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_CACHE_THRESHOLD, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_CACHE_MAX_AGE (location_type l)
+ {
+ return symbol_type (token::TOKEN_CACHE_MAX_AGE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_CACHE_MAX_AGE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_CACHE_MAX_AGE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DECLINE_PROBATION_PERIOD (location_type l)
+ {
+ return symbol_type (token::TOKEN_DECLINE_PROBATION_PERIOD, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DECLINE_PROBATION_PERIOD (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DECLINE_PROBATION_PERIOD, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SERVER_TAG (location_type l)
+ {
+ return symbol_type (token::TOKEN_SERVER_TAG, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SERVER_TAG (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SERVER_TAG, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_STATISTIC_DEFAULT_SAMPLE_COUNT (location_type l)
+ {
+ return symbol_type (token::TOKEN_STATISTIC_DEFAULT_SAMPLE_COUNT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_STATISTIC_DEFAULT_SAMPLE_COUNT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_STATISTIC_DEFAULT_SAMPLE_COUNT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_STATISTIC_DEFAULT_SAMPLE_AGE (location_type l)
+ {
+ return symbol_type (token::TOKEN_STATISTIC_DEFAULT_SAMPLE_AGE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_STATISTIC_DEFAULT_SAMPLE_AGE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_STATISTIC_DEFAULT_SAMPLE_AGE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DDNS_SEND_UPDATES (location_type l)
+ {
+ return symbol_type (token::TOKEN_DDNS_SEND_UPDATES, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DDNS_SEND_UPDATES (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DDNS_SEND_UPDATES, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DDNS_OVERRIDE_NO_UPDATE (location_type l)
+ {
+ return symbol_type (token::TOKEN_DDNS_OVERRIDE_NO_UPDATE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DDNS_OVERRIDE_NO_UPDATE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DDNS_OVERRIDE_NO_UPDATE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DDNS_OVERRIDE_CLIENT_UPDATE (location_type l)
+ {
+ return symbol_type (token::TOKEN_DDNS_OVERRIDE_CLIENT_UPDATE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DDNS_OVERRIDE_CLIENT_UPDATE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DDNS_OVERRIDE_CLIENT_UPDATE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DDNS_REPLACE_CLIENT_NAME (location_type l)
+ {
+ return symbol_type (token::TOKEN_DDNS_REPLACE_CLIENT_NAME, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DDNS_REPLACE_CLIENT_NAME (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DDNS_REPLACE_CLIENT_NAME, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DDNS_GENERATED_PREFIX (location_type l)
+ {
+ return symbol_type (token::TOKEN_DDNS_GENERATED_PREFIX, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DDNS_GENERATED_PREFIX (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DDNS_GENERATED_PREFIX, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DDNS_QUALIFYING_SUFFIX (location_type l)
+ {
+ return symbol_type (token::TOKEN_DDNS_QUALIFYING_SUFFIX, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DDNS_QUALIFYING_SUFFIX (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DDNS_QUALIFYING_SUFFIX, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DDNS_UPDATE_ON_RENEW (location_type l)
+ {
+ return symbol_type (token::TOKEN_DDNS_UPDATE_ON_RENEW, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DDNS_UPDATE_ON_RENEW (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DDNS_UPDATE_ON_RENEW, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DDNS_USE_CONFLICT_RESOLUTION (location_type l)
+ {
+ return symbol_type (token::TOKEN_DDNS_USE_CONFLICT_RESOLUTION, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DDNS_USE_CONFLICT_RESOLUTION (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DDNS_USE_CONFLICT_RESOLUTION, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_STORE_EXTENDED_INFO (location_type l)
+ {
+ return symbol_type (token::TOKEN_STORE_EXTENDED_INFO, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_STORE_EXTENDED_INFO (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_STORE_EXTENDED_INFO, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUBNET4 (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUBNET4, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUBNET4 (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUBNET4, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUBNET_4O6_INTERFACE (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUBNET_4O6_INTERFACE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUBNET_4O6_INTERFACE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUBNET_4O6_INTERFACE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUBNET_4O6_INTERFACE_ID (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUBNET_4O6_INTERFACE_ID, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUBNET_4O6_INTERFACE_ID (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUBNET_4O6_INTERFACE_ID, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUBNET_4O6_SUBNET (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUBNET_4O6_SUBNET, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUBNET_4O6_SUBNET (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUBNET_4O6_SUBNET, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_OPTION_DEF (location_type l)
+ {
+ return symbol_type (token::TOKEN_OPTION_DEF, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_OPTION_DEF (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_OPTION_DEF, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_OPTION_DATA (location_type l)
+ {
+ return symbol_type (token::TOKEN_OPTION_DATA, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_OPTION_DATA (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_OPTION_DATA, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_NAME (location_type l)
+ {
+ return symbol_type (token::TOKEN_NAME, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_NAME (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_NAME, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DATA (location_type l)
+ {
+ return symbol_type (token::TOKEN_DATA, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DATA (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DATA, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_CODE (location_type l)
+ {
+ return symbol_type (token::TOKEN_CODE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_CODE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_CODE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SPACE (location_type l)
+ {
+ return symbol_type (token::TOKEN_SPACE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SPACE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SPACE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_CSV_FORMAT (location_type l)
+ {
+ return symbol_type (token::TOKEN_CSV_FORMAT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_CSV_FORMAT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_CSV_FORMAT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_ALWAYS_SEND (location_type l)
+ {
+ return symbol_type (token::TOKEN_ALWAYS_SEND, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_ALWAYS_SEND (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_ALWAYS_SEND, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_RECORD_TYPES (location_type l)
+ {
+ return symbol_type (token::TOKEN_RECORD_TYPES, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_RECORD_TYPES (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_RECORD_TYPES, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_ENCAPSULATE (location_type l)
+ {
+ return symbol_type (token::TOKEN_ENCAPSULATE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_ENCAPSULATE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_ENCAPSULATE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_ARRAY (location_type l)
+ {
+ return symbol_type (token::TOKEN_ARRAY, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_ARRAY (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_ARRAY, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_PARKED_PACKET_LIMIT (location_type l)
+ {
+ return symbol_type (token::TOKEN_PARKED_PACKET_LIMIT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_PARKED_PACKET_LIMIT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_PARKED_PACKET_LIMIT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SHARED_NETWORKS (location_type l)
+ {
+ return symbol_type (token::TOKEN_SHARED_NETWORKS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SHARED_NETWORKS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SHARED_NETWORKS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_POOLS (location_type l)
+ {
+ return symbol_type (token::TOKEN_POOLS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_POOLS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_POOLS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_POOL (location_type l)
+ {
+ return symbol_type (token::TOKEN_POOL, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_POOL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_POOL, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_USER_CONTEXT (location_type l)
+ {
+ return symbol_type (token::TOKEN_USER_CONTEXT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_USER_CONTEXT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_USER_CONTEXT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_COMMENT (location_type l)
+ {
+ return symbol_type (token::TOKEN_COMMENT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_COMMENT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_COMMENT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUBNET (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUBNET, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUBNET (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUBNET, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_INTERFACE (location_type l)
+ {
+ return symbol_type (token::TOKEN_INTERFACE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_INTERFACE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_INTERFACE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_ID (location_type l)
+ {
+ return symbol_type (token::TOKEN_ID, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_ID (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_ID, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_RESERVATION_MODE (location_type l)
+ {
+ return symbol_type (token::TOKEN_RESERVATION_MODE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_RESERVATION_MODE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_RESERVATION_MODE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DISABLED (location_type l)
+ {
+ return symbol_type (token::TOKEN_DISABLED, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DISABLED (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DISABLED, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_OUT_OF_POOL (location_type l)
+ {
+ return symbol_type (token::TOKEN_OUT_OF_POOL, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_OUT_OF_POOL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_OUT_OF_POOL, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_GLOBAL (location_type l)
+ {
+ return symbol_type (token::TOKEN_GLOBAL, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_GLOBAL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_GLOBAL, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_ALL (location_type l)
+ {
+ return symbol_type (token::TOKEN_ALL, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_ALL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_ALL, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_RESERVATIONS_GLOBAL (location_type l)
+ {
+ return symbol_type (token::TOKEN_RESERVATIONS_GLOBAL, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_RESERVATIONS_GLOBAL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_RESERVATIONS_GLOBAL, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_RESERVATIONS_IN_SUBNET (location_type l)
+ {
+ return symbol_type (token::TOKEN_RESERVATIONS_IN_SUBNET, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_RESERVATIONS_IN_SUBNET (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_RESERVATIONS_IN_SUBNET, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_RESERVATIONS_OUT_OF_POOL (location_type l)
+ {
+ return symbol_type (token::TOKEN_RESERVATIONS_OUT_OF_POOL, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_RESERVATIONS_OUT_OF_POOL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_RESERVATIONS_OUT_OF_POOL, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_HOST_RESERVATION_IDENTIFIERS (location_type l)
+ {
+ return symbol_type (token::TOKEN_HOST_RESERVATION_IDENTIFIERS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_HOST_RESERVATION_IDENTIFIERS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_HOST_RESERVATION_IDENTIFIERS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_CLIENT_CLASSES (location_type l)
+ {
+ return symbol_type (token::TOKEN_CLIENT_CLASSES, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_CLIENT_CLASSES (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_CLIENT_CLASSES, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_REQUIRE_CLIENT_CLASSES (location_type l)
+ {
+ return symbol_type (token::TOKEN_REQUIRE_CLIENT_CLASSES, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_REQUIRE_CLIENT_CLASSES (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_REQUIRE_CLIENT_CLASSES, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_TEST (location_type l)
+ {
+ return symbol_type (token::TOKEN_TEST, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_TEST (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_TEST, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_ONLY_IF_REQUIRED (location_type l)
+ {
+ return symbol_type (token::TOKEN_ONLY_IF_REQUIRED, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_ONLY_IF_REQUIRED (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_ONLY_IF_REQUIRED, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_CLIENT_CLASS (location_type l)
+ {
+ return symbol_type (token::TOKEN_CLIENT_CLASS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_CLIENT_CLASS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_CLIENT_CLASS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_RESERVATIONS (location_type l)
+ {
+ return symbol_type (token::TOKEN_RESERVATIONS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_RESERVATIONS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_RESERVATIONS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DUID (location_type l)
+ {
+ return symbol_type (token::TOKEN_DUID, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DUID (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DUID, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_HW_ADDRESS (location_type l)
+ {
+ return symbol_type (token::TOKEN_HW_ADDRESS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_HW_ADDRESS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_HW_ADDRESS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_CIRCUIT_ID (location_type l)
+ {
+ return symbol_type (token::TOKEN_CIRCUIT_ID, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_CIRCUIT_ID (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_CIRCUIT_ID, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_CLIENT_ID (location_type l)
+ {
+ return symbol_type (token::TOKEN_CLIENT_ID, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_CLIENT_ID (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_CLIENT_ID, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_HOSTNAME (location_type l)
+ {
+ return symbol_type (token::TOKEN_HOSTNAME, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_HOSTNAME (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_HOSTNAME, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_FLEX_ID (location_type l)
+ {
+ return symbol_type (token::TOKEN_FLEX_ID, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_FLEX_ID (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_FLEX_ID, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_RELAY (location_type l)
+ {
+ return symbol_type (token::TOKEN_RELAY, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_RELAY (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_RELAY, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_IP_ADDRESS (location_type l)
+ {
+ return symbol_type (token::TOKEN_IP_ADDRESS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_IP_ADDRESS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_IP_ADDRESS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_IP_ADDRESSES (location_type l)
+ {
+ return symbol_type (token::TOKEN_IP_ADDRESSES, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_IP_ADDRESSES (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_IP_ADDRESSES, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_HOOKS_LIBRARIES (location_type l)
+ {
+ return symbol_type (token::TOKEN_HOOKS_LIBRARIES, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_HOOKS_LIBRARIES (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_HOOKS_LIBRARIES, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_LIBRARY (location_type l)
+ {
+ return symbol_type (token::TOKEN_LIBRARY, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_LIBRARY (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_LIBRARY, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_PARAMETERS (location_type l)
+ {
+ return symbol_type (token::TOKEN_PARAMETERS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_PARAMETERS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_PARAMETERS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_EXPIRED_LEASES_PROCESSING (location_type l)
+ {
+ return symbol_type (token::TOKEN_EXPIRED_LEASES_PROCESSING, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_EXPIRED_LEASES_PROCESSING (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_EXPIRED_LEASES_PROCESSING, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_RECLAIM_TIMER_WAIT_TIME (location_type l)
+ {
+ return symbol_type (token::TOKEN_RECLAIM_TIMER_WAIT_TIME, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_RECLAIM_TIMER_WAIT_TIME (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_RECLAIM_TIMER_WAIT_TIME, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_FLUSH_RECLAIMED_TIMER_WAIT_TIME (location_type l)
+ {
+ return symbol_type (token::TOKEN_FLUSH_RECLAIMED_TIMER_WAIT_TIME, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_FLUSH_RECLAIMED_TIMER_WAIT_TIME (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_FLUSH_RECLAIMED_TIMER_WAIT_TIME, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_HOLD_RECLAIMED_TIME (location_type l)
+ {
+ return symbol_type (token::TOKEN_HOLD_RECLAIMED_TIME, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_HOLD_RECLAIMED_TIME (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_HOLD_RECLAIMED_TIME, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_MAX_RECLAIM_LEASES (location_type l)
+ {
+ return symbol_type (token::TOKEN_MAX_RECLAIM_LEASES, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_MAX_RECLAIM_LEASES (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_MAX_RECLAIM_LEASES, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_MAX_RECLAIM_TIME (location_type l)
+ {
+ return symbol_type (token::TOKEN_MAX_RECLAIM_TIME, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_MAX_RECLAIM_TIME (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_MAX_RECLAIM_TIME, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_UNWARNED_RECLAIM_CYCLES (location_type l)
+ {
+ return symbol_type (token::TOKEN_UNWARNED_RECLAIM_CYCLES, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_UNWARNED_RECLAIM_CYCLES (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_UNWARNED_RECLAIM_CYCLES, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DHCP4O6_PORT (location_type l)
+ {
+ return symbol_type (token::TOKEN_DHCP4O6_PORT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DHCP4O6_PORT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DHCP4O6_PORT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DHCP_MULTI_THREADING (location_type l)
+ {
+ return symbol_type (token::TOKEN_DHCP_MULTI_THREADING, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DHCP_MULTI_THREADING (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DHCP_MULTI_THREADING, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_ENABLE_MULTI_THREADING (location_type l)
+ {
+ return symbol_type (token::TOKEN_ENABLE_MULTI_THREADING, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_ENABLE_MULTI_THREADING (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_ENABLE_MULTI_THREADING, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_THREAD_POOL_SIZE (location_type l)
+ {
+ return symbol_type (token::TOKEN_THREAD_POOL_SIZE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_THREAD_POOL_SIZE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_THREAD_POOL_SIZE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_PACKET_QUEUE_SIZE (location_type l)
+ {
+ return symbol_type (token::TOKEN_PACKET_QUEUE_SIZE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_PACKET_QUEUE_SIZE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_PACKET_QUEUE_SIZE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_CONTROL_SOCKET (location_type l)
+ {
+ return symbol_type (token::TOKEN_CONTROL_SOCKET, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_CONTROL_SOCKET (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_CONTROL_SOCKET, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SOCKET_TYPE (location_type l)
+ {
+ return symbol_type (token::TOKEN_SOCKET_TYPE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SOCKET_TYPE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SOCKET_TYPE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SOCKET_NAME (location_type l)
+ {
+ return symbol_type (token::TOKEN_SOCKET_NAME, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SOCKET_NAME (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SOCKET_NAME, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DHCP_QUEUE_CONTROL (location_type l)
+ {
+ return symbol_type (token::TOKEN_DHCP_QUEUE_CONTROL, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DHCP_QUEUE_CONTROL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DHCP_QUEUE_CONTROL, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_ENABLE_QUEUE (location_type l)
+ {
+ return symbol_type (token::TOKEN_ENABLE_QUEUE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_ENABLE_QUEUE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_ENABLE_QUEUE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_QUEUE_TYPE (location_type l)
+ {
+ return symbol_type (token::TOKEN_QUEUE_TYPE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_QUEUE_TYPE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_QUEUE_TYPE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_CAPACITY (location_type l)
+ {
+ return symbol_type (token::TOKEN_CAPACITY, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_CAPACITY (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_CAPACITY, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DHCP_DDNS (location_type l)
+ {
+ return symbol_type (token::TOKEN_DHCP_DDNS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DHCP_DDNS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DHCP_DDNS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_ENABLE_UPDATES (location_type l)
+ {
+ return symbol_type (token::TOKEN_ENABLE_UPDATES, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_ENABLE_UPDATES (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_ENABLE_UPDATES, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_QUALIFYING_SUFFIX (location_type l)
+ {
+ return symbol_type (token::TOKEN_QUALIFYING_SUFFIX, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_QUALIFYING_SUFFIX (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_QUALIFYING_SUFFIX, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SERVER_IP (location_type l)
+ {
+ return symbol_type (token::TOKEN_SERVER_IP, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SERVER_IP (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SERVER_IP, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SERVER_PORT (location_type l)
+ {
+ return symbol_type (token::TOKEN_SERVER_PORT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SERVER_PORT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SERVER_PORT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SENDER_IP (location_type l)
+ {
+ return symbol_type (token::TOKEN_SENDER_IP, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SENDER_IP (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SENDER_IP, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SENDER_PORT (location_type l)
+ {
+ return symbol_type (token::TOKEN_SENDER_PORT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SENDER_PORT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SENDER_PORT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_MAX_QUEUE_SIZE (location_type l)
+ {
+ return symbol_type (token::TOKEN_MAX_QUEUE_SIZE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_MAX_QUEUE_SIZE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_MAX_QUEUE_SIZE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_NCR_PROTOCOL (location_type l)
+ {
+ return symbol_type (token::TOKEN_NCR_PROTOCOL, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_NCR_PROTOCOL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_NCR_PROTOCOL, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_NCR_FORMAT (location_type l)
+ {
+ return symbol_type (token::TOKEN_NCR_FORMAT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_NCR_FORMAT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_NCR_FORMAT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_OVERRIDE_NO_UPDATE (location_type l)
+ {
+ return symbol_type (token::TOKEN_OVERRIDE_NO_UPDATE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_OVERRIDE_NO_UPDATE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_OVERRIDE_NO_UPDATE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_OVERRIDE_CLIENT_UPDATE (location_type l)
+ {
+ return symbol_type (token::TOKEN_OVERRIDE_CLIENT_UPDATE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_OVERRIDE_CLIENT_UPDATE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_OVERRIDE_CLIENT_UPDATE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_REPLACE_CLIENT_NAME (location_type l)
+ {
+ return symbol_type (token::TOKEN_REPLACE_CLIENT_NAME, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_REPLACE_CLIENT_NAME (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_REPLACE_CLIENT_NAME, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_GENERATED_PREFIX (location_type l)
+ {
+ return symbol_type (token::TOKEN_GENERATED_PREFIX, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_GENERATED_PREFIX (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_GENERATED_PREFIX, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_TCP (location_type l)
+ {
+ return symbol_type (token::TOKEN_TCP, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_TCP (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_TCP, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_JSON (location_type l)
+ {
+ return symbol_type (token::TOKEN_JSON, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_JSON (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_JSON, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_WHEN_PRESENT (location_type l)
+ {
+ return symbol_type (token::TOKEN_WHEN_PRESENT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_WHEN_PRESENT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_WHEN_PRESENT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_NEVER (location_type l)
+ {
+ return symbol_type (token::TOKEN_NEVER, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_NEVER (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_NEVER, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_ALWAYS (location_type l)
+ {
+ return symbol_type (token::TOKEN_ALWAYS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_ALWAYS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_ALWAYS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_WHEN_NOT_PRESENT (location_type l)
+ {
+ return symbol_type (token::TOKEN_WHEN_NOT_PRESENT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_WHEN_NOT_PRESENT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_WHEN_NOT_PRESENT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_HOSTNAME_CHAR_SET (location_type l)
+ {
+ return symbol_type (token::TOKEN_HOSTNAME_CHAR_SET, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_HOSTNAME_CHAR_SET (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_HOSTNAME_CHAR_SET, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_HOSTNAME_CHAR_REPLACEMENT (location_type l)
+ {
+ return symbol_type (token::TOKEN_HOSTNAME_CHAR_REPLACEMENT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_HOSTNAME_CHAR_REPLACEMENT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_HOSTNAME_CHAR_REPLACEMENT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_EARLY_GLOBAL_RESERVATIONS_LOOKUP (location_type l)
+ {
+ return symbol_type (token::TOKEN_EARLY_GLOBAL_RESERVATIONS_LOOKUP, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_EARLY_GLOBAL_RESERVATIONS_LOOKUP (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_EARLY_GLOBAL_RESERVATIONS_LOOKUP, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_IP_RESERVATIONS_UNIQUE (location_type l)
+ {
+ return symbol_type (token::TOKEN_IP_RESERVATIONS_UNIQUE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_IP_RESERVATIONS_UNIQUE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_IP_RESERVATIONS_UNIQUE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_RESERVATIONS_LOOKUP_FIRST (location_type l)
+ {
+ return symbol_type (token::TOKEN_RESERVATIONS_LOOKUP_FIRST, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_RESERVATIONS_LOOKUP_FIRST (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_RESERVATIONS_LOOKUP_FIRST, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_LOGGERS (location_type l)
+ {
+ return symbol_type (token::TOKEN_LOGGERS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_LOGGERS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_LOGGERS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_OUTPUT_OPTIONS (location_type l)
+ {
+ return symbol_type (token::TOKEN_OUTPUT_OPTIONS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_OUTPUT_OPTIONS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_OUTPUT_OPTIONS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_OUTPUT (location_type l)
+ {
+ return symbol_type (token::TOKEN_OUTPUT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_OUTPUT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_OUTPUT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DEBUGLEVEL (location_type l)
+ {
+ return symbol_type (token::TOKEN_DEBUGLEVEL, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DEBUGLEVEL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DEBUGLEVEL, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SEVERITY (location_type l)
+ {
+ return symbol_type (token::TOKEN_SEVERITY, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SEVERITY (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SEVERITY, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_FLUSH (location_type l)
+ {
+ return symbol_type (token::TOKEN_FLUSH, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_FLUSH (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_FLUSH, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_MAXSIZE (location_type l)
+ {
+ return symbol_type (token::TOKEN_MAXSIZE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_MAXSIZE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_MAXSIZE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_MAXVER (location_type l)
+ {
+ return symbol_type (token::TOKEN_MAXVER, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_MAXVER (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_MAXVER, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_PATTERN (location_type l)
+ {
+ return symbol_type (token::TOKEN_PATTERN, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_PATTERN (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_PATTERN, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_COMPATIBILITY (location_type l)
+ {
+ return symbol_type (token::TOKEN_COMPATIBILITY, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_COMPATIBILITY (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_COMPATIBILITY, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_LENIENT_OPTION_PARSING (location_type l)
+ {
+ return symbol_type (token::TOKEN_LENIENT_OPTION_PARSING, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_LENIENT_OPTION_PARSING (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_LENIENT_OPTION_PARSING, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_TOPLEVEL_JSON (location_type l)
+ {
+ return symbol_type (token::TOKEN_TOPLEVEL_JSON, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_TOPLEVEL_JSON (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_TOPLEVEL_JSON, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_TOPLEVEL_DHCP4 (location_type l)
+ {
+ return symbol_type (token::TOKEN_TOPLEVEL_DHCP4, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_TOPLEVEL_DHCP4 (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_TOPLEVEL_DHCP4, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUB_DHCP4 (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUB_DHCP4, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUB_DHCP4 (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUB_DHCP4, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUB_INTERFACES4 (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUB_INTERFACES4, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUB_INTERFACES4 (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUB_INTERFACES4, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUB_SUBNET4 (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUB_SUBNET4, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUB_SUBNET4 (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUB_SUBNET4, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUB_POOL4 (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUB_POOL4, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUB_POOL4 (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUB_POOL4, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUB_RESERVATION (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUB_RESERVATION, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUB_RESERVATION (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUB_RESERVATION, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUB_OPTION_DEFS (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUB_OPTION_DEFS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUB_OPTION_DEFS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUB_OPTION_DEFS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUB_OPTION_DEF (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUB_OPTION_DEF, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUB_OPTION_DEF (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUB_OPTION_DEF, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUB_OPTION_DATA (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUB_OPTION_DATA, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUB_OPTION_DATA (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUB_OPTION_DATA, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUB_HOOKS_LIBRARY (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUB_HOOKS_LIBRARY, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUB_HOOKS_LIBRARY (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUB_HOOKS_LIBRARY, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUB_DHCP_DDNS (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUB_DHCP_DDNS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUB_DHCP_DDNS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUB_DHCP_DDNS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUB_CONFIG_CONTROL (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUB_CONFIG_CONTROL, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUB_CONFIG_CONTROL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUB_CONFIG_CONTROL, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_STRING (std::string v, location_type l)
+ {
+ return symbol_type (token::TOKEN_STRING, std::move (v), std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_STRING (const std::string& v, const location_type& l)
+ {
+ return symbol_type (token::TOKEN_STRING, v, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_INTEGER (int64_t v, location_type l)
+ {
+ return symbol_type (token::TOKEN_INTEGER, std::move (v), std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_INTEGER (const int64_t& v, const location_type& l)
+ {
+ return symbol_type (token::TOKEN_INTEGER, v, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_FLOAT (double v, location_type l)
+ {
+ return symbol_type (token::TOKEN_FLOAT, std::move (v), std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_FLOAT (const double& v, const location_type& l)
+ {
+ return symbol_type (token::TOKEN_FLOAT, v, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_BOOLEAN (bool v, location_type l)
+ {
+ return symbol_type (token::TOKEN_BOOLEAN, std::move (v), std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_BOOLEAN (const bool& v, const location_type& l)
+ {
+ return symbol_type (token::TOKEN_BOOLEAN, v, l);
+ }
+#endif
+
+
+ class context
+ {
+ public:
+ context (const Dhcp4Parser& yyparser, const symbol_type& yyla);
+ const symbol_type& lookahead () const YY_NOEXCEPT { return yyla_; }
+ symbol_kind_type token () const YY_NOEXCEPT { return yyla_.kind (); }
+ const location_type& location () const YY_NOEXCEPT { return yyla_.location; }
+
+ /// Put in YYARG at most YYARGN of the expected tokens, and return the
+ /// number of tokens stored in YYARG. If YYARG is null, return the
+ /// number of expected tokens (guaranteed to be less than YYNTOKENS).
+ int expected_tokens (symbol_kind_type yyarg[], int yyargn) const;
+
+ private:
+ const Dhcp4Parser& yyparser_;
+ const symbol_type& yyla_;
+ };
+
+ private:
+#if YY_CPLUSPLUS < 201103L
+ /// Non copyable.
+ Dhcp4Parser (const Dhcp4Parser&);
+ /// Non copyable.
+ Dhcp4Parser& operator= (const Dhcp4Parser&);
+#endif
+
+
+ /// Stored state numbers (used for stacks).
+ typedef short state_type;
+
+ /// The arguments of the error message.
+ int yy_syntax_error_arguments_ (const context& yyctx,
+ symbol_kind_type yyarg[], int yyargn) const;
+
+ /// Generate an error message.
+ /// \param yyctx the context in which the error occurred.
+ virtual std::string yysyntax_error_ (const context& yyctx) const;
+ /// Compute post-reduction state.
+ /// \param yystate the current state
+ /// \param yysym the nonterminal to push on the stack
+ static state_type yy_lr_goto_state_ (state_type yystate, int yysym);
+
+ /// Whether the given \c yypact_ value indicates a defaulted state.
+ /// \param yyvalue the value to check
+ static bool yy_pact_value_is_default_ (int yyvalue) YY_NOEXCEPT;
+
+ /// Whether the given \c yytable_ value indicates a syntax error.
+ /// \param yyvalue the value to check
+ static bool yy_table_value_is_error_ (int yyvalue) YY_NOEXCEPT;
+
+ static const short yypact_ninf_;
+ static const signed char yytable_ninf_;
+
+ /// Convert a scanner token kind \a t to a symbol kind.
+ /// In theory \a t should be a token_kind_type, but character literals
+ /// are valid, yet not members of the token_kind_type enum.
+ static symbol_kind_type yytranslate_ (int t) YY_NOEXCEPT;
+
+ /// Convert the symbol name \a n to a form suitable for a diagnostic.
+ static std::string yytnamerr_ (const char *yystr);
+
+ /// For a symbol, its name in clear.
+ static const char* const yytname_[];
+
+
+ // Tables.
+ // YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
+ // STATE-NUM.
+ static const short yypact_[];
+
+ // YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM.
+ // Performed when YYTABLE does not specify something else to do. Zero
+ // means the default is an error.
+ static const short yydefact_[];
+
+ // YYPGOTO[NTERM-NUM].
+ static const short yypgoto_[];
+
+ // YYDEFGOTO[NTERM-NUM].
+ static const short yydefgoto_[];
+
+ // YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If
+ // positive, shift that token. If negative, reduce the rule whose
+ // number is the opposite. If YYTABLE_NINF, syntax error.
+ static const short yytable_[];
+
+ static const short yycheck_[];
+
+ // YYSTOS[STATE-NUM] -- The symbol kind of the accessing symbol of
+ // state STATE-NUM.
+ static const short yystos_[];
+
+ // YYR1[RULE-NUM] -- Symbol kind of the left-hand side of rule RULE-NUM.
+ static const short yyr1_[];
+
+ // YYR2[RULE-NUM] -- Number of symbols on the right-hand side of rule RULE-NUM.
+ static const signed char yyr2_[];
+
+
+#if PARSER4_DEBUG
+ // YYRLINE[YYN] -- Source line where rule number YYN was defined.
+ static const short yyrline_[];
+ /// Report on the debug stream that the rule \a r is going to be reduced.
+ virtual void yy_reduce_print_ (int r) const;
+ /// Print the state stack on the debug stream.
+ virtual void yy_stack_print_ () const;
+
+ /// Debugging level.
+ int yydebug_;
+ /// Debug stream.
+ std::ostream* yycdebug_;
+
+ /// \brief Display a symbol kind, value and location.
+ /// \param yyo The output stream.
+ /// \param yysym The symbol.
+ template <typename Base>
+ void yy_print_ (std::ostream& yyo, const basic_symbol<Base>& yysym) const;
+#endif
+
+ /// \brief Reclaim the memory associated to a symbol.
+ /// \param yymsg Why this token is reclaimed.
+ /// If null, print nothing.
+ /// \param yysym The symbol.
+ template <typename Base>
+ void yy_destroy_ (const char* yymsg, basic_symbol<Base>& yysym) const;
+
+ private:
+ /// Type access provider for state based symbols.
+ struct by_state
+ {
+ /// Default constructor.
+ by_state () YY_NOEXCEPT;
+
+ /// The symbol kind as needed by the constructor.
+ typedef state_type kind_type;
+
+ /// Constructor.
+ by_state (kind_type s) YY_NOEXCEPT;
+
+ /// Copy constructor.
+ by_state (const by_state& that) YY_NOEXCEPT;
+
+ /// Record that this symbol is empty.
+ void clear () YY_NOEXCEPT;
+
+ /// Steal the symbol kind from \a that.
+ void move (by_state& that);
+
+ /// The symbol kind (corresponding to \a state).
+ /// \a symbol_kind::S_YYEMPTY when empty.
+ symbol_kind_type kind () const YY_NOEXCEPT;
+
+ /// The state number used to denote an empty symbol.
+ /// We use the initial state, as it does not have a value.
+ enum { empty_state = 0 };
+
+ /// The state.
+ /// \a empty when empty.
+ state_type state;
+ };
+
+ /// "Internal" symbol: element of the stack.
+ struct stack_symbol_type : basic_symbol<by_state>
+ {
+ /// Superclass.
+ typedef basic_symbol<by_state> super_type;
+ /// Construct an empty symbol.
+ stack_symbol_type ();
+ /// Move or copy construction.
+ stack_symbol_type (YY_RVREF (stack_symbol_type) that);
+ /// Steal the contents from \a sym to build this.
+ stack_symbol_type (state_type s, YY_MOVE_REF (symbol_type) sym);
+#if YY_CPLUSPLUS < 201103L
+ /// Assignment, needed by push_back by some old implementations.
+ /// Moves the contents of that.
+ stack_symbol_type& operator= (stack_symbol_type& that);
+
+ /// Assignment, needed by push_back by other implementations.
+ /// Needed by some other old implementations.
+ stack_symbol_type& operator= (const stack_symbol_type& that);
+#endif
+ };
+
+ /// A stack with random access from its top.
+ template <typename T, typename S = std::vector<T> >
+ class stack
+ {
+ public:
+ // Hide our reversed order.
+ typedef typename S::iterator iterator;
+ typedef typename S::const_iterator const_iterator;
+ typedef typename S::size_type size_type;
+ typedef typename std::ptrdiff_t index_type;
+
+ stack (size_type n = 200) YY_NOEXCEPT
+ : seq_ (n)
+ {}
+
+#if 201103L <= YY_CPLUSPLUS
+ /// Non copyable.
+ stack (const stack&) = delete;
+ /// Non copyable.
+ stack& operator= (const stack&) = delete;
+#endif
+
+ /// Random access.
+ ///
+ /// Index 0 returns the topmost element.
+ const T&
+ operator[] (index_type i) const
+ {
+ return seq_[size_type (size () - 1 - i)];
+ }
+
+ /// Random access.
+ ///
+ /// Index 0 returns the topmost element.
+ T&
+ operator[] (index_type i)
+ {
+ return seq_[size_type (size () - 1 - i)];
+ }
+
+ /// Steal the contents of \a t.
+ ///
+ /// Close to move-semantics.
+ void
+ push (YY_MOVE_REF (T) t)
+ {
+ seq_.push_back (T ());
+ operator[] (0).move (t);
+ }
+
+ /// Pop elements from the stack.
+ void
+ pop (std::ptrdiff_t n = 1) YY_NOEXCEPT
+ {
+ for (; 0 < n; --n)
+ seq_.pop_back ();
+ }
+
+ /// Pop all elements from the stack.
+ void
+ clear () YY_NOEXCEPT
+ {
+ seq_.clear ();
+ }
+
+ /// Number of elements on the stack.
+ index_type
+ size () const YY_NOEXCEPT
+ {
+ return index_type (seq_.size ());
+ }
+
+ /// Iterator on top of the stack (going downwards).
+ const_iterator
+ begin () const YY_NOEXCEPT
+ {
+ return seq_.begin ();
+ }
+
+ /// Bottom of the stack.
+ const_iterator
+ end () const YY_NOEXCEPT
+ {
+ return seq_.end ();
+ }
+
+ /// Present a slice of the top of a stack.
+ class slice
+ {
+ public:
+ slice (const stack& stack, index_type range) YY_NOEXCEPT
+ : stack_ (stack)
+ , range_ (range)
+ {}
+
+ const T&
+ operator[] (index_type i) const
+ {
+ return stack_[range_ - i];
+ }
+
+ private:
+ const stack& stack_;
+ index_type range_;
+ };
+
+ private:
+#if YY_CPLUSPLUS < 201103L
+ /// Non copyable.
+ stack (const stack&);
+ /// Non copyable.
+ stack& operator= (const stack&);
+#endif
+ /// The wrapped container.
+ S seq_;
+ };
+
+
+ /// Stack type.
+ typedef stack<stack_symbol_type> stack_type;
+
+ /// The stack.
+ stack_type yystack_;
+
+ /// Push a new state on the stack.
+ /// \param m a debug message to display
+ /// if null, no trace is output.
+ /// \param sym the symbol
+ /// \warning the contents of \a s.value is stolen.
+ void yypush_ (const char* m, YY_MOVE_REF (stack_symbol_type) sym);
+
+ /// Push a new look ahead token on the state on the stack.
+ /// \param m a debug message to display
+ /// if null, no trace is output.
+ /// \param s the state
+ /// \param sym the symbol (for its value and location).
+ /// \warning the contents of \a sym.value is stolen.
+ void yypush_ (const char* m, state_type s, YY_MOVE_REF (symbol_type) sym);
+
+ /// Pop \a n symbols from the stack.
+ void yypop_ (int n = 1) YY_NOEXCEPT;
+
+ /// Constants.
+ enum
+ {
+ yylast_ = 1262, ///< Last index in yytable_.
+ yynnts_ = 422, ///< Number of nonterminal symbols.
+ yyfinal_ = 28 ///< Termination state number.
+ };
+
+
+ // User arguments.
+ isc::dhcp::Parser4Context& ctx;
+
+ };
+
+ inline
+ Dhcp4Parser::symbol_kind_type
+ Dhcp4Parser::yytranslate_ (int t) YY_NOEXCEPT
+ {
+ // YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to
+ // TOKEN-NUM as returned by yylex.
+ static
+ const unsigned char
+ translate_table[] =
+ {
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
+ 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
+ 55, 56, 57, 58, 59, 60, 61, 62, 63, 64,
+ 65, 66, 67, 68, 69, 70, 71, 72, 73, 74,
+ 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,
+ 85, 86, 87, 88, 89, 90, 91, 92, 93, 94,
+ 95, 96, 97, 98, 99, 100, 101, 102, 103, 104,
+ 105, 106, 107, 108, 109, 110, 111, 112, 113, 114,
+ 115, 116, 117, 118, 119, 120, 121, 122, 123, 124,
+ 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
+ 135, 136, 137, 138, 139, 140, 141, 142, 143, 144,
+ 145, 146, 147, 148, 149, 150, 151, 152, 153, 154,
+ 155, 156, 157, 158, 159, 160, 161, 162, 163, 164,
+ 165, 166, 167, 168, 169, 170, 171, 172, 173, 174,
+ 175, 176, 177, 178, 179, 180, 181, 182, 183, 184,
+ 185, 186, 187, 188, 189, 190, 191, 192, 193, 194,
+ 195, 196, 197, 198, 199, 200, 201, 202, 203, 204,
+ 205
+ };
+ // Last valid token kind.
+ const int code_max = 460;
+
+ if (t <= 0)
+ return symbol_kind::S_YYEOF;
+ else if (t <= code_max)
+ return static_cast <symbol_kind_type> (translate_table[t]);
+ else
+ return symbol_kind::S_YYUNDEF;
+ }
+
+ // basic_symbol.
+ template <typename Base>
+ Dhcp4Parser::basic_symbol<Base>::basic_symbol (const basic_symbol& that)
+ : Base (that)
+ , value ()
+ , location (that.location)
+ {
+ switch (this->kind ())
+ {
+ case symbol_kind::S_value: // value
+ case symbol_kind::S_map_value: // map_value
+ case symbol_kind::S_ddns_replace_client_name_value: // ddns_replace_client_name_value
+ case symbol_kind::S_socket_type: // socket_type
+ case symbol_kind::S_outbound_interface_value: // outbound_interface_value
+ case symbol_kind::S_db_type: // db_type
+ case symbol_kind::S_on_fail_mode: // on_fail_mode
+ case symbol_kind::S_hr_mode: // hr_mode
+ case symbol_kind::S_ncr_protocol_value: // ncr_protocol_value
+ value.copy< ElementPtr > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_BOOLEAN: // "boolean"
+ value.copy< bool > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_FLOAT: // "floating point"
+ value.copy< double > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_INTEGER: // "integer"
+ value.copy< int64_t > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ value.copy< std::string > (YY_MOVE (that.value));
+ break;
+
+ default:
+ break;
+ }
+
+ }
+
+
+
+
+ template <typename Base>
+ Dhcp4Parser::symbol_kind_type
+ Dhcp4Parser::basic_symbol<Base>::type_get () const YY_NOEXCEPT
+ {
+ return this->kind ();
+ }
+
+
+ template <typename Base>
+ bool
+ Dhcp4Parser::basic_symbol<Base>::empty () const YY_NOEXCEPT
+ {
+ return this->kind () == symbol_kind::S_YYEMPTY;
+ }
+
+ template <typename Base>
+ void
+ Dhcp4Parser::basic_symbol<Base>::move (basic_symbol& s)
+ {
+ super_type::move (s);
+ switch (this->kind ())
+ {
+ case symbol_kind::S_value: // value
+ case symbol_kind::S_map_value: // map_value
+ case symbol_kind::S_ddns_replace_client_name_value: // ddns_replace_client_name_value
+ case symbol_kind::S_socket_type: // socket_type
+ case symbol_kind::S_outbound_interface_value: // outbound_interface_value
+ case symbol_kind::S_db_type: // db_type
+ case symbol_kind::S_on_fail_mode: // on_fail_mode
+ case symbol_kind::S_hr_mode: // hr_mode
+ case symbol_kind::S_ncr_protocol_value: // ncr_protocol_value
+ value.move< ElementPtr > (YY_MOVE (s.value));
+ break;
+
+ case symbol_kind::S_BOOLEAN: // "boolean"
+ value.move< bool > (YY_MOVE (s.value));
+ break;
+
+ case symbol_kind::S_FLOAT: // "floating point"
+ value.move< double > (YY_MOVE (s.value));
+ break;
+
+ case symbol_kind::S_INTEGER: // "integer"
+ value.move< int64_t > (YY_MOVE (s.value));
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ value.move< std::string > (YY_MOVE (s.value));
+ break;
+
+ default:
+ break;
+ }
+
+ location = YY_MOVE (s.location);
+ }
+
+ // by_kind.
+ inline
+ Dhcp4Parser::by_kind::by_kind () YY_NOEXCEPT
+ : kind_ (symbol_kind::S_YYEMPTY)
+ {}
+
+#if 201103L <= YY_CPLUSPLUS
+ inline
+ Dhcp4Parser::by_kind::by_kind (by_kind&& that) YY_NOEXCEPT
+ : kind_ (that.kind_)
+ {
+ that.clear ();
+ }
+#endif
+
+ inline
+ Dhcp4Parser::by_kind::by_kind (const by_kind& that) YY_NOEXCEPT
+ : kind_ (that.kind_)
+ {}
+
+ inline
+ Dhcp4Parser::by_kind::by_kind (token_kind_type t) YY_NOEXCEPT
+ : kind_ (yytranslate_ (t))
+ {}
+
+
+
+ inline
+ void
+ Dhcp4Parser::by_kind::clear () YY_NOEXCEPT
+ {
+ kind_ = symbol_kind::S_YYEMPTY;
+ }
+
+ inline
+ void
+ Dhcp4Parser::by_kind::move (by_kind& that)
+ {
+ kind_ = that.kind_;
+ that.clear ();
+ }
+
+ inline
+ Dhcp4Parser::symbol_kind_type
+ Dhcp4Parser::by_kind::kind () const YY_NOEXCEPT
+ {
+ return kind_;
+ }
+
+
+ inline
+ Dhcp4Parser::symbol_kind_type
+ Dhcp4Parser::by_kind::type_get () const YY_NOEXCEPT
+ {
+ return this->kind ();
+ }
+
+
+#line 14 "dhcp4_parser.yy"
+} } // isc::dhcp
+#line 5421 "dhcp4_parser.h"
+
+
+
+
+#endif // !YY_PARSER4_DHCP4_PARSER_H_INCLUDED
diff --git a/src/bin/dhcp4/dhcp4_parser.yy b/src/bin/dhcp4/dhcp4_parser.yy
new file mode 100644
index 0000000..6e5f551
--- /dev/null
+++ b/src/bin/dhcp4/dhcp4_parser.yy
@@ -0,0 +1,2866 @@
+/* Copyright (C) 2016-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/. */
+
+%skeleton "lalr1.cc" /* -*- C++ -*- */
+%require "3.3.0"
+%defines
+%define api.parser.class {Dhcp4Parser}
+%define api.prefix {parser4_}
+%define api.token.constructor
+%define api.value.type variant
+%define api.namespace {isc::dhcp}
+%define parse.assert
+%code requires
+{
+#include <string>
+#include <cc/data.h>
+#include <dhcp/option.h>
+#include <boost/lexical_cast.hpp>
+#include <dhcp4/parser_context_decl.h>
+
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace std;
+}
+// The parsing context.
+%param { isc::dhcp::Parser4Context& ctx }
+%locations
+%define parse.trace
+%define parse.error verbose
+%code
+{
+#include <dhcp4/parser_context.h>
+}
+
+
+%define api.token.prefix {TOKEN_}
+// Tokens in an order which makes sense and related to the intended use.
+// Actual regexps for tokens are defined in dhcp4_lexer.ll.
+%token
+ END 0 "end of file"
+ COMMA ","
+ COLON ":"
+ LSQUARE_BRACKET "["
+ RSQUARE_BRACKET "]"
+ LCURLY_BRACKET "{"
+ RCURLY_BRACKET "}"
+ NULL_TYPE "null"
+
+ DHCP4 "Dhcp4"
+
+ CONFIG_CONTROL "config-control"
+ CONFIG_DATABASES "config-databases"
+ CONFIG_FETCH_WAIT_TIME "config-fetch-wait-time"
+
+ INTERFACES_CONFIG "interfaces-config"
+ INTERFACES "interfaces"
+ DHCP_SOCKET_TYPE "dhcp-socket-type"
+ RAW "raw"
+ UDP "udp"
+ OUTBOUND_INTERFACE "outbound-interface"
+ SAME_AS_INBOUND "same-as-inbound"
+ USE_ROUTING "use-routing"
+ RE_DETECT "re-detect"
+ SERVICE_SOCKETS_REQUIRE_ALL "service-sockets-require-all"
+ SERVICE_SOCKETS_RETRY_WAIT_TIME "service-sockets-retry-wait-time"
+ SERVICE_SOCKETS_MAX_RETRIES "service-sockets-max-retries"
+
+ SANITY_CHECKS "sanity-checks"
+ LEASE_CHECKS "lease-checks"
+
+ ECHO_CLIENT_ID "echo-client-id"
+ MATCH_CLIENT_ID "match-client-id"
+ AUTHORITATIVE "authoritative"
+ NEXT_SERVER "next-server"
+ SERVER_HOSTNAME "server-hostname"
+ BOOT_FILE_NAME "boot-file-name"
+
+ LEASE_DATABASE "lease-database"
+ HOSTS_DATABASE "hosts-database"
+ HOSTS_DATABASES "hosts-databases"
+ TYPE "type"
+ MEMFILE "memfile"
+ MYSQL "mysql"
+ POSTGRESQL "postgresql"
+ USER "user"
+ PASSWORD "password"
+ HOST "host"
+ PORT "port"
+ PERSIST "persist"
+ LFC_INTERVAL "lfc-interval"
+ READONLY "readonly"
+ CONNECT_TIMEOUT "connect-timeout"
+ MAX_RECONNECT_TRIES "max-reconnect-tries"
+ RECONNECT_WAIT_TIME "reconnect-wait-time"
+ ON_FAIL "on-fail"
+ STOP_RETRY_EXIT "stop-retry-exit"
+ SERVE_RETRY_EXIT "serve-retry-exit"
+ SERVE_RETRY_CONTINUE "serve-retry-continue"
+ MAX_ROW_ERRORS "max-row-errors"
+ TRUST_ANCHOR "trust-anchor"
+ CERT_FILE "cert-file"
+ KEY_FILE "key-file"
+ CIPHER_LIST "cipher-list"
+
+ VALID_LIFETIME "valid-lifetime"
+ MIN_VALID_LIFETIME "min-valid-lifetime"
+ MAX_VALID_LIFETIME "max-valid-lifetime"
+ RENEW_TIMER "renew-timer"
+ REBIND_TIMER "rebind-timer"
+ CALCULATE_TEE_TIMES "calculate-tee-times"
+ T1_PERCENT "t1-percent"
+ T2_PERCENT "t2-percent"
+ CACHE_THRESHOLD "cache-threshold"
+ CACHE_MAX_AGE "cache-max-age"
+ DECLINE_PROBATION_PERIOD "decline-probation-period"
+ SERVER_TAG "server-tag"
+ STATISTIC_DEFAULT_SAMPLE_COUNT "statistic-default-sample-count"
+ STATISTIC_DEFAULT_SAMPLE_AGE "statistic-default-sample-age"
+ DDNS_SEND_UPDATES "ddns-send-updates"
+ DDNS_OVERRIDE_NO_UPDATE "ddns-override-no-update"
+ DDNS_OVERRIDE_CLIENT_UPDATE "ddns-override-client-update"
+ DDNS_REPLACE_CLIENT_NAME "ddns-replace-client-name"
+ DDNS_GENERATED_PREFIX "ddns-generated-prefix"
+ DDNS_QUALIFYING_SUFFIX "ddns-qualifying-suffix"
+ DDNS_UPDATE_ON_RENEW "ddns-update-on-renew"
+ DDNS_USE_CONFLICT_RESOLUTION "ddns-use-conflict-resolution"
+ STORE_EXTENDED_INFO "store-extended-info"
+ SUBNET4 "subnet4"
+ SUBNET_4O6_INTERFACE "4o6-interface"
+ SUBNET_4O6_INTERFACE_ID "4o6-interface-id"
+ SUBNET_4O6_SUBNET "4o6-subnet"
+ OPTION_DEF "option-def"
+ OPTION_DATA "option-data"
+ NAME "name"
+ DATA "data"
+ CODE "code"
+ SPACE "space"
+ CSV_FORMAT "csv-format"
+ ALWAYS_SEND "always-send"
+ RECORD_TYPES "record-types"
+ ENCAPSULATE "encapsulate"
+ ARRAY "array"
+ PARKED_PACKET_LIMIT "parked-packet-limit"
+
+ SHARED_NETWORKS "shared-networks"
+
+ POOLS "pools"
+ POOL "pool"
+ USER_CONTEXT "user-context"
+ COMMENT "comment"
+
+ SUBNET "subnet"
+ INTERFACE "interface"
+ ID "id"
+ RESERVATION_MODE "reservation-mode"
+ DISABLED "disabled"
+ OUT_OF_POOL "out-of-pool"
+ GLOBAL "global"
+ ALL "all"
+ RESERVATIONS_GLOBAL "reservations-global"
+ RESERVATIONS_IN_SUBNET "reservations-in-subnet"
+ RESERVATIONS_OUT_OF_POOL "reservations-out-of-pool"
+
+ HOST_RESERVATION_IDENTIFIERS "host-reservation-identifiers"
+
+ CLIENT_CLASSES "client-classes"
+ REQUIRE_CLIENT_CLASSES "require-client-classes"
+ TEST "test"
+ ONLY_IF_REQUIRED "only-if-required"
+ CLIENT_CLASS "client-class"
+
+ RESERVATIONS "reservations"
+ DUID "duid"
+ HW_ADDRESS "hw-address"
+ CIRCUIT_ID "circuit-id"
+ CLIENT_ID "client-id"
+ HOSTNAME "hostname"
+ FLEX_ID "flex-id"
+
+ RELAY "relay"
+ IP_ADDRESS "ip-address"
+ IP_ADDRESSES "ip-addresses"
+
+ HOOKS_LIBRARIES "hooks-libraries"
+ LIBRARY "library"
+ PARAMETERS "parameters"
+
+ EXPIRED_LEASES_PROCESSING "expired-leases-processing"
+ RECLAIM_TIMER_WAIT_TIME "reclaim-timer-wait-time"
+ FLUSH_RECLAIMED_TIMER_WAIT_TIME "flush-reclaimed-timer-wait-time"
+ HOLD_RECLAIMED_TIME "hold-reclaimed-time"
+ MAX_RECLAIM_LEASES "max-reclaim-leases"
+ MAX_RECLAIM_TIME "max-reclaim-time"
+ UNWARNED_RECLAIM_CYCLES "unwarned-reclaim-cycles"
+
+ DHCP4O6_PORT "dhcp4o6-port"
+
+ DHCP_MULTI_THREADING "multi-threading"
+ ENABLE_MULTI_THREADING "enable-multi-threading"
+ THREAD_POOL_SIZE "thread-pool-size"
+ PACKET_QUEUE_SIZE "packet-queue-size"
+
+ CONTROL_SOCKET "control-socket"
+ SOCKET_TYPE "socket-type"
+ SOCKET_NAME "socket-name"
+
+ DHCP_QUEUE_CONTROL "dhcp-queue-control"
+ ENABLE_QUEUE "enable-queue"
+ QUEUE_TYPE "queue-type"
+ CAPACITY "capacity"
+
+ DHCP_DDNS "dhcp-ddns"
+ ENABLE_UPDATES "enable-updates"
+ QUALIFYING_SUFFIX "qualifying-suffix"
+ SERVER_IP "server-ip"
+ SERVER_PORT "server-port"
+ SENDER_IP "sender-ip"
+ SENDER_PORT "sender-port"
+ MAX_QUEUE_SIZE "max-queue-size"
+ NCR_PROTOCOL "ncr-protocol"
+ NCR_FORMAT "ncr-format"
+ OVERRIDE_NO_UPDATE "override-no-update"
+ OVERRIDE_CLIENT_UPDATE "override-client-update"
+ REPLACE_CLIENT_NAME "replace-client-name"
+ GENERATED_PREFIX "generated-prefix"
+ TCP "tcp"
+ JSON "JSON"
+ WHEN_PRESENT "when-present"
+ NEVER "never"
+ ALWAYS "always"
+ WHEN_NOT_PRESENT "when-not-present"
+ HOSTNAME_CHAR_SET "hostname-char-set"
+ HOSTNAME_CHAR_REPLACEMENT "hostname-char-replacement"
+ EARLY_GLOBAL_RESERVATIONS_LOOKUP "early-global-reservations-lookup"
+ IP_RESERVATIONS_UNIQUE "ip-reservations-unique"
+ RESERVATIONS_LOOKUP_FIRST "reservations-lookup-first"
+
+ LOGGERS "loggers"
+ OUTPUT_OPTIONS "output_options"
+ OUTPUT "output"
+ DEBUGLEVEL "debuglevel"
+ SEVERITY "severity"
+ FLUSH "flush"
+ MAXSIZE "maxsize"
+ MAXVER "maxver"
+ PATTERN "pattern"
+
+ COMPATIBILITY "compatibility"
+ LENIENT_OPTION_PARSING "lenient-option-parsing"
+
+ // Not real tokens, just a way to signal what the parser is expected to
+ // parse.
+ TOPLEVEL_JSON
+ TOPLEVEL_DHCP4
+ SUB_DHCP4
+ SUB_INTERFACES4
+ SUB_SUBNET4
+ SUB_POOL4
+ SUB_RESERVATION
+ SUB_OPTION_DEFS
+ SUB_OPTION_DEF
+ SUB_OPTION_DATA
+ SUB_HOOKS_LIBRARY
+ SUB_DHCP_DDNS
+ SUB_CONFIG_CONTROL
+;
+
+%token <std::string> STRING "constant string"
+%token <int64_t> INTEGER "integer"
+%token <double> FLOAT "floating point"
+%token <bool> BOOLEAN "boolean"
+
+%type <ElementPtr> value
+%type <ElementPtr> map_value
+%type <ElementPtr> socket_type
+%type <ElementPtr> outbound_interface_value
+%type <ElementPtr> db_type
+%type <ElementPtr> on_fail_mode
+%type <ElementPtr> hr_mode
+%type <ElementPtr> ncr_protocol_value
+%type <ElementPtr> ddns_replace_client_name_value
+
+%printer { yyoutput << $$; } <*>;
+
+%%
+
+// The whole grammar starts with a map, because the config file
+// consists of Dhcp, Logger and DhcpDdns entries in one big { }.
+// We made the same for subparsers at the exception of the JSON value.
+%start start;
+
+start: TOPLEVEL_JSON { ctx.ctx_ = ctx.NO_KEYWORD; } sub_json
+ | TOPLEVEL_DHCP4 { ctx.ctx_ = ctx.CONFIG; } syntax_map
+ | SUB_DHCP4 { ctx.ctx_ = ctx.DHCP4; } sub_dhcp4
+ | SUB_INTERFACES4 { ctx.ctx_ = ctx.INTERFACES_CONFIG; } sub_interfaces4
+ | SUB_SUBNET4 { ctx.ctx_ = ctx.SUBNET4; } sub_subnet4
+ | SUB_POOL4 { ctx.ctx_ = ctx.POOLS; } sub_pool4
+ | SUB_RESERVATION { ctx.ctx_ = ctx.RESERVATIONS; } sub_reservation
+ | SUB_OPTION_DEFS { ctx.ctx_ = ctx.DHCP4; } sub_option_def_list
+ | SUB_OPTION_DEF { ctx.ctx_ = ctx.OPTION_DEF; } sub_option_def
+ | SUB_OPTION_DATA { ctx.ctx_ = ctx.OPTION_DATA; } sub_option_data
+ | SUB_HOOKS_LIBRARY { ctx.ctx_ = ctx.HOOKS_LIBRARIES; } sub_hooks_library
+ | SUB_DHCP_DDNS { ctx.ctx_ = ctx.DHCP_DDNS; } sub_dhcp_ddns
+ | SUB_CONFIG_CONTROL { ctx.ctx_ = ctx.CONFIG_CONTROL; } sub_config_control
+ ;
+
+// ---- generic JSON parser ---------------------------------
+
+// Note that ctx_ is NO_KEYWORD here
+
+// Values rule
+value: INTEGER { $$ = ElementPtr(new IntElement($1, ctx.loc2pos(@1))); }
+ | FLOAT { $$ = ElementPtr(new DoubleElement($1, ctx.loc2pos(@1))); }
+ | BOOLEAN { $$ = ElementPtr(new BoolElement($1, ctx.loc2pos(@1))); }
+ | STRING { $$ = ElementPtr(new StringElement($1, ctx.loc2pos(@1))); }
+ | NULL_TYPE { $$ = ElementPtr(new NullElement(ctx.loc2pos(@1))); }
+ | map2 { $$ = ctx.stack_.back(); ctx.stack_.pop_back(); }
+ | list_generic { $$ = ctx.stack_.back(); ctx.stack_.pop_back(); }
+ ;
+
+sub_json: value {
+ // Push back the JSON value on the stack
+ ctx.stack_.push_back($1);
+};
+
+map2: LCURLY_BRACKET {
+ // This code is executed when we're about to start parsing
+ // the content of the map
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(m);
+} map_content RCURLY_BRACKET {
+ // map parsing completed. If we ever want to do any wrap up
+ // (maybe some sanity checking), this would be the best place
+ // for it.
+};
+
+map_value: map2 { $$ = ctx.stack_.back(); ctx.stack_.pop_back(); };
+
+// Assignments rule
+map_content: %empty // empty map
+ | not_empty_map
+ ;
+
+not_empty_map: STRING COLON value {
+ // map containing a single entry
+ ctx.unique($1, ctx.loc2pos(@1));
+ ctx.stack_.back()->set($1, $3);
+ }
+ | not_empty_map COMMA STRING COLON value {
+ // map consisting of a shorter map followed by
+ // comma and string:value
+ ctx.unique($3, ctx.loc2pos(@3));
+ ctx.stack_.back()->set($3, $5);
+ }
+ | not_empty_map COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+list_generic: LSQUARE_BRACKET {
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(l);
+} list_content RSQUARE_BRACKET {
+ // list parsing complete. Put any sanity checking here
+};
+
+list_content: %empty // Empty list
+ | not_empty_list
+ ;
+
+not_empty_list: value {
+ // List consisting of a single element.
+ ctx.stack_.back()->add($1);
+ }
+ | not_empty_list COMMA value {
+ // List ending with , and a value.
+ ctx.stack_.back()->add($3);
+ }
+ | not_empty_list COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+// This one is used in syntax parser and is restricted to strings.
+list_strings: LSQUARE_BRACKET {
+ // List parsing about to start
+} list_strings_content RSQUARE_BRACKET {
+ // list parsing complete. Put any sanity checking here
+ //ctx.stack_.pop_back();
+};
+
+list_strings_content: %empty // Empty list
+ | not_empty_list_strings
+ ;
+
+not_empty_list_strings: STRING {
+ ElementPtr s(new StringElement($1, ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(s);
+ }
+ | not_empty_list_strings COMMA STRING {
+ ElementPtr s(new StringElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->add(s);
+ }
+ | not_empty_list_strings COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+// ---- generic JSON parser ends here ----------------------------------
+
+// ---- syntax checking parser starts here -----------------------------
+
+// Unknown keyword in a map
+unknown_map_entry: STRING COLON {
+ const std::string& where = ctx.contextName();
+ const std::string& keyword = $1;
+ error(@1,
+ "got unexpected keyword \"" + keyword + "\" in " + where + " map.");
+};
+
+
+// This defines the top-level { } that holds Dhcp4 only object.
+syntax_map: LCURLY_BRACKET {
+ // This code is executed when we're about to start parsing
+ // the content of the map
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(m);
+} global_object RCURLY_BRACKET {
+ // map parsing completed. If we ever want to do any wrap up
+ // (maybe some sanity checking), this would be the best place
+ // for it.
+
+ // Dhcp4 is required
+ ctx.require("Dhcp4", ctx.loc2pos(@1), ctx.loc2pos(@4));
+};
+
+// This represents the single top level entry, e.g. Dhcp4.
+global_object: DHCP4 {
+ // This code is executed when we're about to start parsing
+ // the content of the map
+ // Prevent against duplicate.
+ ctx.unique("Dhcp4", ctx.loc2pos(@1));
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("Dhcp4", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.DHCP4);
+} COLON LCURLY_BRACKET global_params RCURLY_BRACKET {
+ // No global parameter is required
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+ | global_object_comma
+ ;
+
+global_object_comma: global_object COMMA {
+ ctx.warnAboutExtraCommas(@2);
+};
+
+// subparser: similar to the corresponding rule but without parent
+// so the stack is empty at the rule entry.
+sub_dhcp4: LCURLY_BRACKET {
+ // Parse the Dhcp4 map
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(m);
+} global_params RCURLY_BRACKET {
+ // No global parameter is required
+ // parsing completed
+};
+
+global_params: global_param
+ | global_params COMMA global_param
+ | global_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+// These are the parameters that are allowed in the top-level for
+// Dhcp4.
+global_param: valid_lifetime
+ | min_valid_lifetime
+ | max_valid_lifetime
+ | renew_timer
+ | rebind_timer
+ | decline_probation_period
+ | subnet4_list
+ | shared_networks
+ | interfaces_config
+ | lease_database
+ | hosts_database
+ | hosts_databases
+ | host_reservation_identifiers
+ | client_classes
+ | option_def_list
+ | option_data_list
+ | hooks_libraries
+ | expired_leases_processing
+ | dhcp4o6_port
+ | control_socket
+ | dhcp_queue_control
+ | dhcp_ddns
+ | echo_client_id
+ | match_client_id
+ | authoritative
+ | next_server
+ | server_hostname
+ | boot_file_name
+ | user_context
+ | comment
+ | sanity_checks
+ | reservations
+ | config_control
+ | server_tag
+ | reservation_mode
+ | reservations_global
+ | reservations_in_subnet
+ | reservations_out_of_pool
+ | calculate_tee_times
+ | t1_percent
+ | t2_percent
+ | cache_threshold
+ | cache_max_age
+ | loggers
+ | hostname_char_set
+ | hostname_char_replacement
+ | ddns_send_updates
+ | ddns_override_no_update
+ | ddns_override_client_update
+ | ddns_replace_client_name
+ | ddns_generated_prefix
+ | ddns_qualifying_suffix
+ | ddns_update_on_renew
+ | ddns_use_conflict_resolution
+ | store_extended_info
+ | statistic_default_sample_count
+ | statistic_default_sample_age
+ | dhcp_multi_threading
+ | early_global_reservations_lookup
+ | ip_reservations_unique
+ | reservations_lookup_first
+ | compatibility
+ | parked_packet_limit
+ | unknown_map_entry
+ ;
+
+valid_lifetime: VALID_LIFETIME COLON INTEGER {
+ ctx.unique("valid-lifetime", ctx.loc2pos(@1));
+ ElementPtr prf(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("valid-lifetime", prf);
+};
+
+min_valid_lifetime: MIN_VALID_LIFETIME COLON INTEGER {
+ ctx.unique("min-valid-lifetime", ctx.loc2pos(@1));
+ ElementPtr prf(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("min-valid-lifetime", prf);
+};
+
+max_valid_lifetime: MAX_VALID_LIFETIME COLON INTEGER {
+ ctx.unique("max-valid-lifetime", ctx.loc2pos(@1));
+ ElementPtr prf(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("max-valid-lifetime", prf);
+};
+
+renew_timer: RENEW_TIMER COLON INTEGER {
+ ctx.unique("renew-timer", ctx.loc2pos(@1));
+ ElementPtr prf(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("renew-timer", prf);
+};
+
+rebind_timer: REBIND_TIMER COLON INTEGER {
+ ctx.unique("rebind-timer", ctx.loc2pos(@1));
+ ElementPtr prf(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("rebind-timer", prf);
+};
+
+calculate_tee_times: CALCULATE_TEE_TIMES COLON BOOLEAN {
+ ctx.unique("calculate-tee-times", ctx.loc2pos(@1));
+ ElementPtr ctt(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("calculate-tee-times", ctt);
+};
+
+t1_percent: T1_PERCENT COLON FLOAT {
+ ctx.unique("t1-percent", ctx.loc2pos(@1));
+ ElementPtr t1(new DoubleElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("t1-percent", t1);
+};
+
+t2_percent: T2_PERCENT COLON FLOAT {
+ ctx.unique("t2-percent", ctx.loc2pos(@1));
+ ElementPtr t2(new DoubleElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("t2-percent", t2);
+};
+
+cache_threshold: CACHE_THRESHOLD COLON FLOAT {
+ ctx.unique("cache-threshold", ctx.loc2pos(@1));
+ ElementPtr ct(new DoubleElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("cache-threshold", ct);
+};
+
+cache_max_age: CACHE_MAX_AGE COLON INTEGER {
+ ctx.unique("cache-max-age", ctx.loc2pos(@1));
+ ElementPtr cm(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("cache-max-age", cm);
+};
+
+decline_probation_period: DECLINE_PROBATION_PERIOD COLON INTEGER {
+ ctx.unique("decline-probation-period", ctx.loc2pos(@1));
+ ElementPtr dpp(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("decline-probation-period", dpp);
+};
+
+server_tag: SERVER_TAG {
+ ctx.unique("server-tag", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr stag(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("server-tag", stag);
+ ctx.leave();
+};
+
+parked_packet_limit: PARKED_PACKET_LIMIT COLON INTEGER {
+ ctx.unique("parked-packet-limit", ctx.loc2pos(@1));
+ ElementPtr ppl(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("parked-packet-limit", ppl);
+};
+
+echo_client_id: ECHO_CLIENT_ID COLON BOOLEAN {
+ ctx.unique("echo-client-id", ctx.loc2pos(@1));
+ ElementPtr echo(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("echo-client-id", echo);
+};
+
+match_client_id: MATCH_CLIENT_ID COLON BOOLEAN {
+ ctx.unique("match-client-id", ctx.loc2pos(@1));
+ ElementPtr match(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("match-client-id", match);
+};
+
+authoritative: AUTHORITATIVE COLON BOOLEAN {
+ ctx.unique("authoritative", ctx.loc2pos(@1));
+ ElementPtr prf(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("authoritative", prf);
+};
+
+ddns_send_updates: DDNS_SEND_UPDATES COLON BOOLEAN {
+ ctx.unique("ddns-send-updates", ctx.loc2pos(@1));
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("ddns-send-updates", b);
+};
+
+ddns_override_no_update: DDNS_OVERRIDE_NO_UPDATE COLON BOOLEAN {
+ ctx.unique("ddns-override-no-update", ctx.loc2pos(@1));
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("ddns-override-no-update", b);
+};
+
+ddns_override_client_update: DDNS_OVERRIDE_CLIENT_UPDATE COLON BOOLEAN {
+ ctx.unique("ddns-override-client-update", ctx.loc2pos(@1));
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("ddns-override-client-update", b);
+};
+
+ddns_replace_client_name: DDNS_REPLACE_CLIENT_NAME {
+ ctx.enter(ctx.REPLACE_CLIENT_NAME);
+ ctx.unique("ddns-replace-client-name", ctx.loc2pos(@1));
+} COLON ddns_replace_client_name_value {
+ ctx.stack_.back()->set("ddns-replace-client-name", $4);
+ ctx.leave();
+};
+
+ddns_replace_client_name_value:
+ WHEN_PRESENT {
+ $$ = ElementPtr(new StringElement("when-present", ctx.loc2pos(@1)));
+ }
+ | NEVER {
+ $$ = ElementPtr(new StringElement("never", ctx.loc2pos(@1)));
+ }
+ | ALWAYS {
+ $$ = ElementPtr(new StringElement("always", ctx.loc2pos(@1)));
+ }
+ | WHEN_NOT_PRESENT {
+ $$ = ElementPtr(new StringElement("when-not-present", ctx.loc2pos(@1)));
+ }
+ | BOOLEAN {
+ error(@1, "boolean values for the replace-client-name are "
+ "no longer supported");
+ }
+ ;
+
+ddns_generated_prefix: DDNS_GENERATED_PREFIX {
+ ctx.unique("ddns-generated-prefix", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr s(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("ddns-generated-prefix", s);
+ ctx.leave();
+};
+
+ddns_qualifying_suffix: DDNS_QUALIFYING_SUFFIX {
+ ctx.unique("ddns-qualifying-suffix", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr s(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("ddns-qualifying-suffix", s);
+ ctx.leave();
+};
+
+ddns_update_on_renew: DDNS_UPDATE_ON_RENEW COLON BOOLEAN {
+ ctx.unique("ddns-update-on-renew", ctx.loc2pos(@1));
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("ddns-update-on-renew", b);
+};
+
+ddns_use_conflict_resolution: DDNS_USE_CONFLICT_RESOLUTION COLON BOOLEAN {
+ ctx.unique("ddns-use-conflict-resolution", ctx.loc2pos(@1));
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("ddns-use-conflict-resolution", b);
+};
+
+hostname_char_set: HOSTNAME_CHAR_SET {
+ ctx.unique("hostname-char-set", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr s(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("hostname-char-set", s);
+ ctx.leave();
+};
+
+hostname_char_replacement: HOSTNAME_CHAR_REPLACEMENT {
+ ctx.unique("hostname-char-replacement", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr s(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("hostname-char-replacement", s);
+ ctx.leave();
+};
+
+store_extended_info: STORE_EXTENDED_INFO COLON BOOLEAN {
+ ctx.unique("store-extended-info", ctx.loc2pos(@1));
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("store-extended-info", b);
+};
+
+statistic_default_sample_count: STATISTIC_DEFAULT_SAMPLE_COUNT COLON INTEGER {
+ ctx.unique("statistic-default-sample-count", ctx.loc2pos(@1));
+ ElementPtr count(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("statistic-default-sample-count", count);
+};
+
+statistic_default_sample_age: STATISTIC_DEFAULT_SAMPLE_AGE COLON INTEGER {
+ ctx.unique("statistic-default-sample-age", ctx.loc2pos(@1));
+ ElementPtr age(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("statistic-default-sample-age", age);
+};
+
+early_global_reservations_lookup: EARLY_GLOBAL_RESERVATIONS_LOOKUP COLON BOOLEAN {
+ ctx.unique("early-global-reservations-lookup", ctx.loc2pos(@1));
+ ElementPtr early(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("early-global-reservations-lookup", early);
+};
+
+ip_reservations_unique: IP_RESERVATIONS_UNIQUE COLON BOOLEAN {
+ ctx.unique("ip-reservations-unique", ctx.loc2pos(@1));
+ ElementPtr unique(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("ip-reservations-unique", unique);
+};
+
+reservations_lookup_first: RESERVATIONS_LOOKUP_FIRST COLON BOOLEAN {
+ ctx.unique("reservations-lookup-first", ctx.loc2pos(@1));
+ ElementPtr first(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("reservations-lookup-first", first);
+};
+
+interfaces_config: INTERFACES_CONFIG {
+ ctx.unique("interfaces-config", ctx.loc2pos(@1));
+ ElementPtr i(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("interfaces-config", i);
+ ctx.stack_.push_back(i);
+ ctx.enter(ctx.INTERFACES_CONFIG);
+} COLON LCURLY_BRACKET interfaces_config_params RCURLY_BRACKET {
+ // No interfaces config param is required
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+interfaces_config_params: interfaces_config_param
+ | interfaces_config_params COMMA interfaces_config_param
+ | interfaces_config_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+interfaces_config_param: interfaces_list
+ | dhcp_socket_type
+ | outbound_interface
+ | re_detect
+ | service_sockets_require_all
+ | service_sockets_retry_wait_time
+ | service_sockets_max_retries
+ | user_context
+ | comment
+ | unknown_map_entry
+ ;
+
+sub_interfaces4: LCURLY_BRACKET {
+ // Parse the interfaces-config map
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(m);
+} interfaces_config_params RCURLY_BRACKET {
+ // No interfaces config param is required
+ // parsing completed
+};
+
+interfaces_list: INTERFACES {
+ ctx.unique("interfaces", ctx.loc2pos(@1));
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("interfaces", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON list_strings {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+dhcp_socket_type: DHCP_SOCKET_TYPE {
+ ctx.unique("dhcp-socket-type", ctx.loc2pos(@1));
+ ctx.enter(ctx.DHCP_SOCKET_TYPE);
+} COLON socket_type {
+ ctx.stack_.back()->set("dhcp-socket-type", $4);
+ ctx.leave();
+};
+
+socket_type: RAW { $$ = ElementPtr(new StringElement("raw", ctx.loc2pos(@1))); }
+ | UDP { $$ = ElementPtr(new StringElement("udp", ctx.loc2pos(@1))); }
+ ;
+
+outbound_interface: OUTBOUND_INTERFACE {
+ ctx.unique("outbound-interface", ctx.loc2pos(@1));
+ ctx.enter(ctx.OUTBOUND_INTERFACE);
+} COLON outbound_interface_value {
+ ctx.stack_.back()->set("outbound-interface", $4);
+ ctx.leave();
+};
+
+outbound_interface_value: SAME_AS_INBOUND {
+ $$ = ElementPtr(new StringElement("same-as-inbound", ctx.loc2pos(@1)));
+} | USE_ROUTING {
+ $$ = ElementPtr(new StringElement("use-routing", ctx.loc2pos(@1)));
+ } ;
+
+re_detect: RE_DETECT COLON BOOLEAN {
+ ctx.unique("re-detect", ctx.loc2pos(@1));
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("re-detect", b);
+};
+
+service_sockets_require_all: SERVICE_SOCKETS_REQUIRE_ALL COLON BOOLEAN {
+ ctx.unique("service-sockets-require-all", ctx.loc2pos(@1));
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("service-sockets-require-all", b);
+};
+
+service_sockets_retry_wait_time: SERVICE_SOCKETS_RETRY_WAIT_TIME COLON INTEGER {
+ ctx.unique("service-sockets-retry-wait-time", ctx.loc2pos(@1));
+ ElementPtr n(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("service-sockets-retry-wait-time", n);
+};
+
+service_sockets_max_retries: SERVICE_SOCKETS_MAX_RETRIES COLON INTEGER {
+ ctx.unique("service-sockets-max-retries", ctx.loc2pos(@1));
+ ElementPtr n(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("service-sockets-max-retries", n);
+};
+
+lease_database: LEASE_DATABASE {
+ ctx.unique("lease-database", ctx.loc2pos(@1));
+ ElementPtr i(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("lease-database", i);
+ ctx.stack_.push_back(i);
+ ctx.enter(ctx.LEASE_DATABASE);
+} COLON LCURLY_BRACKET database_map_params RCURLY_BRACKET {
+ // The type parameter is required
+ ctx.require("type", ctx.loc2pos(@4), ctx.loc2pos(@6));
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+sanity_checks: SANITY_CHECKS {
+ ctx.unique("sanity-checks", ctx.loc2pos(@1));
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("sanity-checks", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.SANITY_CHECKS);
+} COLON LCURLY_BRACKET sanity_checks_params RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+sanity_checks_params: sanity_checks_param
+ | sanity_checks_params COMMA sanity_checks_param
+ | sanity_checks_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+sanity_checks_param: lease_checks;
+
+lease_checks: LEASE_CHECKS {
+ ctx.unique("lease-checks", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+
+ if ( (string($4) == "none") ||
+ (string($4) == "warn") ||
+ (string($4) == "fix") ||
+ (string($4) == "fix-del") ||
+ (string($4) == "del")) {
+ ElementPtr user(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("lease-checks", user);
+ ctx.leave();
+ } else {
+ error(@4, "Unsupported 'lease-checks value: " + string($4) +
+ ", supported values are: none, warn, fix, fix-del, del");
+ }
+}
+
+hosts_database: HOSTS_DATABASE {
+ ctx.unique("hosts-database", ctx.loc2pos(@1));
+ ElementPtr i(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("hosts-database", i);
+ ctx.stack_.push_back(i);
+ ctx.enter(ctx.HOSTS_DATABASE);
+} COLON LCURLY_BRACKET database_map_params RCURLY_BRACKET {
+ // The type parameter is required
+ ctx.require("type", ctx.loc2pos(@4), ctx.loc2pos(@6));
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+hosts_databases: HOSTS_DATABASES {
+ ctx.unique("hosts-databases", ctx.loc2pos(@1));
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("hosts-databases", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.HOSTS_DATABASE);
+} COLON LSQUARE_BRACKET database_list RSQUARE_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+database_list: %empty
+ | not_empty_database_list
+ ;
+
+not_empty_database_list: database
+ | not_empty_database_list COMMA database
+ | not_empty_database_list COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+database: LCURLY_BRACKET {
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+} database_map_params RCURLY_BRACKET {
+ // The type parameter is required
+ ctx.require("type", ctx.loc2pos(@1), ctx.loc2pos(@4));
+ ctx.stack_.pop_back();
+};
+
+database_map_params: database_map_param
+ | database_map_params COMMA database_map_param
+ | database_map_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+database_map_param: database_type
+ | user
+ | password
+ | host
+ | port
+ | name
+ | persist
+ | lfc_interval
+ | readonly
+ | connect_timeout
+ | max_reconnect_tries
+ | reconnect_wait_time
+ | on_fail
+ | max_row_errors
+ | trust_anchor
+ | cert_file
+ | key_file
+ | cipher_list
+ | unknown_map_entry
+ ;
+
+database_type: TYPE {
+ ctx.unique("type", ctx.loc2pos(@1));
+ ctx.enter(ctx.DATABASE_TYPE);
+} COLON db_type {
+ ctx.stack_.back()->set("type", $4);
+ ctx.leave();
+};
+
+db_type: MEMFILE { $$ = ElementPtr(new StringElement("memfile", ctx.loc2pos(@1))); }
+ | MYSQL { $$ = ElementPtr(new StringElement("mysql", ctx.loc2pos(@1))); }
+ | POSTGRESQL { $$ = ElementPtr(new StringElement("postgresql", ctx.loc2pos(@1))); }
+ ;
+
+user: USER {
+ ctx.unique("user", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr user(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("user", user);
+ ctx.leave();
+};
+
+password: PASSWORD {
+ ctx.unique("password", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr pwd(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("password", pwd);
+ ctx.leave();
+};
+
+host: HOST {
+ ctx.unique("host", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr h(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("host", h);
+ ctx.leave();
+};
+
+port: PORT COLON INTEGER {
+ ctx.unique("port", ctx.loc2pos(@1));
+ ElementPtr p(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("port", p);
+};
+
+name: NAME {
+ ctx.unique("name", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr name(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("name", name);
+ ctx.leave();
+};
+
+persist: PERSIST COLON BOOLEAN {
+ ctx.unique("persist", ctx.loc2pos(@1));
+ ElementPtr n(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("persist", n);
+};
+
+lfc_interval: LFC_INTERVAL COLON INTEGER {
+ ctx.unique("lfc-interval", ctx.loc2pos(@1));
+ ElementPtr n(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("lfc-interval", n);
+};
+
+readonly: READONLY COLON BOOLEAN {
+ ctx.unique("readonly", ctx.loc2pos(@1));
+ ElementPtr n(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("readonly", n);
+};
+
+connect_timeout: CONNECT_TIMEOUT COLON INTEGER {
+ ctx.unique("connect-timeout", ctx.loc2pos(@1));
+ ElementPtr n(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("connect-timeout", n);
+};
+
+max_reconnect_tries: MAX_RECONNECT_TRIES COLON INTEGER {
+ ctx.unique("max-reconnect-tries", ctx.loc2pos(@1));
+ ElementPtr n(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("max-reconnect-tries", n);
+};
+
+reconnect_wait_time: RECONNECT_WAIT_TIME COLON INTEGER {
+ ctx.unique("reconnect-wait-time", ctx.loc2pos(@1));
+ ElementPtr n(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("reconnect-wait-time", n);
+};
+
+on_fail: ON_FAIL {
+ ctx.unique("on-fail", ctx.loc2pos(@1));
+ ctx.enter(ctx.DATABASE_ON_FAIL);
+} COLON on_fail_mode {
+ ctx.stack_.back()->set("on-fail", $4);
+ ctx.leave();
+};
+
+on_fail_mode: STOP_RETRY_EXIT { $$ = ElementPtr(new StringElement("stop-retry-exit", ctx.loc2pos(@1))); }
+ | SERVE_RETRY_EXIT { $$ = ElementPtr(new StringElement("serve-retry-exit", ctx.loc2pos(@1))); }
+ | SERVE_RETRY_CONTINUE { $$ = ElementPtr(new StringElement("serve-retry-continue", ctx.loc2pos(@1))); }
+ ;
+
+max_row_errors: MAX_ROW_ERRORS COLON INTEGER {
+ ctx.unique("max-row-errors", ctx.loc2pos(@1));
+ ElementPtr n(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("max-row-errors", n);
+};
+
+trust_anchor: TRUST_ANCHOR {
+ ctx.unique("trust-anchor", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr ca(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("trust-anchor", ca);
+ ctx.leave();
+};
+
+cert_file: CERT_FILE {
+ ctx.unique("cert-file", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr cert(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("cert-file", cert);
+ ctx.leave();
+};
+
+key_file: KEY_FILE {
+ ctx.unique("key-file", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr key(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("key-file", key);
+ ctx.leave();
+};
+
+cipher_list: CIPHER_LIST {
+ ctx.unique("cipher-list", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr cl(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("cipher-list", cl);
+ ctx.leave();
+};
+
+host_reservation_identifiers: HOST_RESERVATION_IDENTIFIERS {
+ ctx.unique("host-reservation-identifiers", ctx.loc2pos(@1));
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("host-reservation-identifiers", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.HOST_RESERVATION_IDENTIFIERS);
+} COLON LSQUARE_BRACKET host_reservation_identifiers_list RSQUARE_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+host_reservation_identifiers_list: host_reservation_identifier
+ | host_reservation_identifiers_list COMMA host_reservation_identifier
+ | host_reservation_identifiers_list COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+host_reservation_identifier: duid_id
+ | hw_address_id
+ | circuit_id
+ | client_id
+ | flex_id
+ ;
+
+duid_id: DUID {
+ ElementPtr duid(new StringElement("duid", ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(duid);
+};
+
+hw_address_id: HW_ADDRESS {
+ ElementPtr hwaddr(new StringElement("hw-address", ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(hwaddr);
+};
+
+circuit_id: CIRCUIT_ID {
+ ElementPtr circuit(new StringElement("circuit-id", ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(circuit);
+};
+
+client_id: CLIENT_ID {
+ ElementPtr client(new StringElement("client-id", ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(client);
+};
+
+flex_id: FLEX_ID {
+ ElementPtr flex_id(new StringElement("flex-id", ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(flex_id);
+};
+
+// --- multi-threading ------------------------------------------------
+
+dhcp_multi_threading: DHCP_MULTI_THREADING {
+ ctx.unique("multi-threading", ctx.loc2pos(@1));
+ ElementPtr mt(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("multi-threading", mt);
+ ctx.stack_.push_back(mt);
+ ctx.enter(ctx.DHCP_MULTI_THREADING);
+} COLON LCURLY_BRACKET multi_threading_params RCURLY_BRACKET {
+ // The enable parameter is required.
+ ctx.require("enable-multi-threading", ctx.loc2pos(@4), ctx.loc2pos(@6));
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+multi_threading_params: multi_threading_param
+ | multi_threading_params COMMA multi_threading_param
+ | multi_threading_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+multi_threading_param: enable_multi_threading
+ | thread_pool_size
+ | packet_queue_size
+ | user_context
+ | comment
+ | unknown_map_entry
+ ;
+
+enable_multi_threading: ENABLE_MULTI_THREADING COLON BOOLEAN {
+ ctx.unique("enable-multi-threading", ctx.loc2pos(@1));
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("enable-multi-threading", b);
+};
+
+thread_pool_size: THREAD_POOL_SIZE COLON INTEGER {
+ ctx.unique("thread-pool-size", ctx.loc2pos(@1));
+ ElementPtr prf(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("thread-pool-size", prf);
+};
+
+packet_queue_size: PACKET_QUEUE_SIZE COLON INTEGER {
+ ctx.unique("packet-queue-size", ctx.loc2pos(@1));
+ ElementPtr prf(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("packet-queue-size", prf);
+};
+
+hooks_libraries: HOOKS_LIBRARIES {
+ ctx.unique("hooks-libraries", ctx.loc2pos(@1));
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("hooks-libraries", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.HOOKS_LIBRARIES);
+} COLON LSQUARE_BRACKET hooks_libraries_list RSQUARE_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+hooks_libraries_list: %empty
+ | not_empty_hooks_libraries_list
+ ;
+
+not_empty_hooks_libraries_list: hooks_library
+ | not_empty_hooks_libraries_list COMMA hooks_library
+ | not_empty_hooks_libraries_list COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+hooks_library: LCURLY_BRACKET {
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+} hooks_params RCURLY_BRACKET {
+ // The library hooks parameter is required
+ ctx.require("library", ctx.loc2pos(@1), ctx.loc2pos(@4));
+ ctx.stack_.pop_back();
+};
+
+sub_hooks_library: LCURLY_BRACKET {
+ // Parse the hooks-libraries list entry map
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(m);
+} hooks_params RCURLY_BRACKET {
+ // The library hooks parameter is required
+ ctx.require("library", ctx.loc2pos(@1), ctx.loc2pos(@4));
+ // parsing completed
+};
+
+hooks_params: hooks_param
+ | hooks_params COMMA hooks_param
+ | hooks_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ | unknown_map_entry
+ ;
+
+hooks_param: library
+ | parameters
+ ;
+
+library: LIBRARY {
+ ctx.unique("library", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr lib(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("library", lib);
+ ctx.leave();
+};
+
+parameters: PARAMETERS {
+ ctx.unique("parameters", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON map_value {
+ ctx.stack_.back()->set("parameters", $4);
+ ctx.leave();
+};
+
+// --- expired-leases-processing ------------------------
+expired_leases_processing: EXPIRED_LEASES_PROCESSING {
+ ctx.unique("expired-leases-processing", ctx.loc2pos(@1));
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("expired-leases-processing", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.EXPIRED_LEASES_PROCESSING);
+} COLON LCURLY_BRACKET expired_leases_params RCURLY_BRACKET {
+ // No expired lease parameter is required
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+expired_leases_params: expired_leases_param
+ | expired_leases_params COMMA expired_leases_param
+ | expired_leases_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+expired_leases_param: reclaim_timer_wait_time
+ | flush_reclaimed_timer_wait_time
+ | hold_reclaimed_time
+ | max_reclaim_leases
+ | max_reclaim_time
+ | unwarned_reclaim_cycles
+ ;
+
+reclaim_timer_wait_time: RECLAIM_TIMER_WAIT_TIME COLON INTEGER {
+ ctx.unique("reclaim-timer-wait-time", ctx.loc2pos(@1));
+ ElementPtr value(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("reclaim-timer-wait-time", value);
+};
+
+flush_reclaimed_timer_wait_time: FLUSH_RECLAIMED_TIMER_WAIT_TIME COLON INTEGER {
+ ctx.unique("flush-reclaimed-timer-wait-time", ctx.loc2pos(@1));
+ ElementPtr value(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("flush-reclaimed-timer-wait-time", value);
+};
+
+hold_reclaimed_time: HOLD_RECLAIMED_TIME COLON INTEGER {
+ ctx.unique("hold-reclaimed-time", ctx.loc2pos(@1));
+ ElementPtr value(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("hold-reclaimed-time", value);
+};
+
+max_reclaim_leases: MAX_RECLAIM_LEASES COLON INTEGER {
+ ctx.unique("max-reclaim-leases", ctx.loc2pos(@1));
+ ElementPtr value(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("max-reclaim-leases", value);
+};
+
+max_reclaim_time: MAX_RECLAIM_TIME COLON INTEGER {
+ ctx.unique("max-reclaim-time", ctx.loc2pos(@1));
+ ElementPtr value(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("max-reclaim-time", value);
+};
+
+unwarned_reclaim_cycles: UNWARNED_RECLAIM_CYCLES COLON INTEGER {
+ ctx.unique("unwarned-reclaim-cycles", ctx.loc2pos(@1));
+ ElementPtr value(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("unwarned-reclaim-cycles", value);
+};
+
+// --- subnet4 ------------------------------------------
+// This defines subnet4 as a list of maps.
+// "subnet4": [ ... ]
+subnet4_list: SUBNET4 {
+ ctx.unique("subnet4", ctx.loc2pos(@1));
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("subnet4", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.SUBNET4);
+} COLON LSQUARE_BRACKET subnet4_list_content RSQUARE_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+// This defines the ... in "subnet4": [ ... ]
+// It can either be empty (no subnets defined), have one subnet
+// or have multiple subnets separate by comma.
+subnet4_list_content: %empty
+ | not_empty_subnet4_list
+ ;
+
+not_empty_subnet4_list: subnet4
+ | not_empty_subnet4_list COMMA subnet4
+ | not_empty_subnet4_list COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+// --- Subnet definitions -------------------------------
+
+// This defines a single subnet, i.e. a single map with
+// subnet4 array.
+subnet4: LCURLY_BRACKET {
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+} subnet4_params RCURLY_BRACKET {
+ // Once we reached this place, the subnet parsing is now complete.
+ // If we want to, we can implement default values here.
+ // In particular we can do things like this:
+ // if (!ctx.stack_.back()->get("interface")) {
+ // ctx.stack_.back()->set("interface", StringElement("loopback"));
+ // }
+ //
+ // We can also stack up one level (Dhcp4) and copy over whatever
+ // global parameters we want to:
+ // if (!ctx.stack_.back()->get("renew-timer")) {
+ // ElementPtr renew = ctx_stack_[...].get("renew-timer");
+ // if (renew) {
+ // ctx.stack_.back()->set("renew-timer", renew);
+ // }
+ // }
+
+ // The subnet subnet4 parameter is required
+ ctx.require("subnet", ctx.loc2pos(@1), ctx.loc2pos(@4));
+ ctx.stack_.pop_back();
+};
+
+sub_subnet4: LCURLY_BRACKET {
+ // Parse the subnet4 list entry map
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(m);
+} subnet4_params RCURLY_BRACKET {
+ // The subnet subnet4 parameter is required
+ ctx.require("subnet", ctx.loc2pos(@1), ctx.loc2pos(@4));
+ // parsing completed
+};
+
+// This defines that subnet can have one or more parameters.
+subnet4_params: subnet4_param
+ | subnet4_params COMMA subnet4_param
+ | subnet4_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+// This defines a list of allowed parameters for each subnet.
+subnet4_param: valid_lifetime
+ | min_valid_lifetime
+ | max_valid_lifetime
+ | renew_timer
+ | rebind_timer
+ | option_data_list
+ | pools_list
+ | subnet
+ | interface
+ | id
+ | client_class
+ | require_client_classes
+ | reservations
+ | reservation_mode
+ | reservations_global
+ | reservations_in_subnet
+ | reservations_out_of_pool
+ | relay
+ | match_client_id
+ | authoritative
+ | next_server
+ | server_hostname
+ | boot_file_name
+ | subnet_4o6_interface
+ | subnet_4o6_interface_id
+ | subnet_4o6_subnet
+ | user_context
+ | comment
+ | calculate_tee_times
+ | t1_percent
+ | t2_percent
+ | cache_threshold
+ | cache_max_age
+ | ddns_send_updates
+ | ddns_override_no_update
+ | ddns_override_client_update
+ | ddns_replace_client_name
+ | ddns_generated_prefix
+ | ddns_qualifying_suffix
+ | ddns_update_on_renew
+ | ddns_use_conflict_resolution
+ | hostname_char_set
+ | hostname_char_replacement
+ | store_extended_info
+ | unknown_map_entry
+ ;
+
+subnet: SUBNET {
+ ctx.unique("subnet", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr subnet(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("subnet", subnet);
+ ctx.leave();
+};
+
+subnet_4o6_interface: SUBNET_4O6_INTERFACE {
+ ctx.unique("4o6-interface", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr iface(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("4o6-interface", iface);
+ ctx.leave();
+};
+
+subnet_4o6_interface_id: SUBNET_4O6_INTERFACE_ID {
+ ctx.unique("4o6-interface-id", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr iface(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("4o6-interface-id", iface);
+ ctx.leave();
+};
+
+subnet_4o6_subnet: SUBNET_4O6_SUBNET {
+ ctx.unique("4o6-subnet", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr iface(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("4o6-subnet", iface);
+ ctx.leave();
+};
+
+interface: INTERFACE {
+ ctx.unique("interface", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr iface(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("interface", iface);
+ ctx.leave();
+};
+
+client_class: CLIENT_CLASS {
+ ctx.unique("client-class", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr cls(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("client-class", cls);
+ ctx.leave();
+};
+
+require_client_classes: REQUIRE_CLIENT_CLASSES {
+ ctx.unique("require-client-classes", ctx.loc2pos(@1));
+ ElementPtr c(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("require-client-classes", c);
+ ctx.stack_.push_back(c);
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON list_strings {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+reservations_global: RESERVATIONS_GLOBAL COLON BOOLEAN {
+ ctx.unique("reservations-global", ctx.loc2pos(@1));
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("reservations-global", b);
+};
+
+reservations_in_subnet: RESERVATIONS_IN_SUBNET COLON BOOLEAN {
+ ctx.unique("reservations-in-subnet", ctx.loc2pos(@1));
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("reservations-in-subnet", b);
+};
+
+reservations_out_of_pool: RESERVATIONS_OUT_OF_POOL COLON BOOLEAN {
+ ctx.unique("reservations-out-of-pool", ctx.loc2pos(@1));
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("reservations-out-of-pool", b);
+};
+
+reservation_mode: RESERVATION_MODE {
+ ctx.unique("reservation-mode", ctx.loc2pos(@1));
+ ctx.enter(ctx.RESERVATION_MODE);
+} COLON hr_mode {
+ ctx.stack_.back()->set("reservation-mode", $4);
+ ctx.leave();
+};
+
+hr_mode: DISABLED { $$ = ElementPtr(new StringElement("disabled", ctx.loc2pos(@1))); }
+ | OUT_OF_POOL { $$ = ElementPtr(new StringElement("out-of-pool", ctx.loc2pos(@1))); }
+ | GLOBAL { $$ = ElementPtr(new StringElement("global", ctx.loc2pos(@1))); }
+ | ALL { $$ = ElementPtr(new StringElement("all", ctx.loc2pos(@1))); }
+ ;
+
+id: ID COLON INTEGER {
+ ctx.unique("id", ctx.loc2pos(@1));
+ ElementPtr id(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("id", id);
+};
+
+// ---- shared-networks ---------------------
+
+shared_networks: SHARED_NETWORKS {
+ ctx.unique("shared-networks", ctx.loc2pos(@1));
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("shared-networks", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.SHARED_NETWORK);
+} COLON LSQUARE_BRACKET shared_networks_content RSQUARE_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+// This allows 0 or more shared network definitions.
+shared_networks_content: %empty
+ | shared_networks_list
+ ;
+
+// This allows 1 or more shared network definitions.
+shared_networks_list: shared_network
+ | shared_networks_list COMMA shared_network
+ | shared_networks_list COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+shared_network: LCURLY_BRACKET {
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+} shared_network_params RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+};
+
+shared_network_params: shared_network_param
+ | shared_network_params COMMA shared_network_param
+ | shared_network_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+shared_network_param: name
+ | subnet4_list
+ | interface
+ | renew_timer
+ | rebind_timer
+ | option_data_list
+ | match_client_id
+ | authoritative
+ | next_server
+ | server_hostname
+ | boot_file_name
+ | relay
+ | reservation_mode
+ | reservations_global
+ | reservations_in_subnet
+ | reservations_out_of_pool
+ | client_class
+ | require_client_classes
+ | valid_lifetime
+ | min_valid_lifetime
+ | max_valid_lifetime
+ | user_context
+ | comment
+ | calculate_tee_times
+ | t1_percent
+ | t2_percent
+ | cache_threshold
+ | cache_max_age
+ | ddns_send_updates
+ | ddns_override_no_update
+ | ddns_override_client_update
+ | ddns_replace_client_name
+ | ddns_generated_prefix
+ | ddns_qualifying_suffix
+ | ddns_update_on_renew
+ | ddns_use_conflict_resolution
+ | hostname_char_set
+ | hostname_char_replacement
+ | store_extended_info
+ | unknown_map_entry
+ ;
+
+// ---- option-def --------------------------
+
+// This defines the "option-def": [ ... ] entry that may appear
+// at a global option.
+option_def_list: OPTION_DEF {
+ ctx.unique("option-def", ctx.loc2pos(@1));
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("option-def", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.OPTION_DEF);
+} COLON LSQUARE_BRACKET option_def_list_content RSQUARE_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+// This defines the top level scope when the parser is told to parse
+// option definitions. It works as a subset limited to option
+// definitions
+sub_option_def_list: LCURLY_BRACKET {
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(m);
+} option_def_list RCURLY_BRACKET {
+ // parsing completed
+};
+
+// This defines the content of option-def. It may be empty,
+// have one entry or multiple entries separated by comma.
+option_def_list_content: %empty
+ | not_empty_option_def_list
+ ;
+
+not_empty_option_def_list: option_def_entry
+ | not_empty_option_def_list COMMA option_def_entry
+ | not_empty_option_def_list COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+// This defines the content of a single entry { ... } within
+// option-def list.
+option_def_entry: LCURLY_BRACKET {
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+} option_def_params RCURLY_BRACKET {
+ // The name, code and type option def parameters are required.
+ ctx.require("name", ctx.loc2pos(@1), ctx.loc2pos(@4));
+ ctx.require("code", ctx.loc2pos(@1), ctx.loc2pos(@4));
+ ctx.require("type", ctx.loc2pos(@1), ctx.loc2pos(@4));
+ ctx.stack_.pop_back();
+};
+
+// This defines the top level scope when the parser is told to parse a single
+// option definition. It's almost exactly the same as option_def_entry, except
+// that it does leave its value on stack.
+sub_option_def: LCURLY_BRACKET {
+ // Parse the option-def list entry map
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(m);
+} option_def_params RCURLY_BRACKET {
+ // The name, code and type option def parameters are required.
+ ctx.require("name", ctx.loc2pos(@1), ctx.loc2pos(@4));
+ ctx.require("code", ctx.loc2pos(@1), ctx.loc2pos(@4));
+ ctx.require("type", ctx.loc2pos(@1), ctx.loc2pos(@4));
+ // parsing completed
+};
+
+// This defines parameters specified inside the map that itself
+// is an entry in option-def list.
+option_def_params: %empty
+ | not_empty_option_def_params
+ ;
+
+not_empty_option_def_params: option_def_param
+ | not_empty_option_def_params COMMA option_def_param
+ | not_empty_option_def_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+option_def_param: option_def_name
+ | option_def_code
+ | option_def_type
+ | option_def_record_types
+ | option_def_space
+ | option_def_encapsulate
+ | option_def_array
+ | user_context
+ | comment
+ | unknown_map_entry
+ ;
+
+option_def_name: name;
+
+code: CODE COLON INTEGER {
+ ctx.unique("code", ctx.loc2pos(@1));
+ ElementPtr code(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("code", code);
+};
+
+option_def_code: code;
+
+option_def_type: TYPE {
+ ctx.unique("type", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr prf(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("type", prf);
+ ctx.leave();
+};
+
+option_def_record_types: RECORD_TYPES {
+ ctx.unique("record-types", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr rtypes(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("record-types", rtypes);
+ ctx.leave();
+};
+
+space: SPACE {
+ ctx.unique("space", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr space(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("space", space);
+ ctx.leave();
+};
+
+option_def_space: space;
+
+option_def_encapsulate: ENCAPSULATE {
+ ctx.unique("encapsulate", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr encap(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("encapsulate", encap);
+ ctx.leave();
+};
+
+option_def_array: ARRAY COLON BOOLEAN {
+ ctx.unique("array", ctx.loc2pos(@1));
+ ElementPtr array(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("array", array);
+};
+
+// ---- option-data --------------------------
+
+// This defines the "option-data": [ ... ] entry that may appear
+// in several places, but most notably in subnet4 entries.
+option_data_list: OPTION_DATA {
+ ctx.unique("option-data", ctx.loc2pos(@1));
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("option-data", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.OPTION_DATA);
+} COLON LSQUARE_BRACKET option_data_list_content RSQUARE_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+// This defines the content of option-data. It may be empty,
+// have one entry or multiple entries separated by comma.
+option_data_list_content: %empty
+ | not_empty_option_data_list
+ ;
+
+// This defines the content of option-data list. It can either
+// be a single value or multiple entries separated by comma.
+not_empty_option_data_list: option_data_entry
+ | not_empty_option_data_list COMMA option_data_entry
+ | not_empty_option_data_list COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+// This defines th content of a single entry { ... } within
+// option-data list.
+option_data_entry: LCURLY_BRACKET {
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+} option_data_params RCURLY_BRACKET {
+ /// @todo: the code or name parameters are required.
+ ctx.stack_.pop_back();
+};
+
+// This defines the top level scope when the parser is told to parse a single
+// option data. It's almost exactly the same as option_data_entry, except
+// that it does leave its value on stack.
+sub_option_data: LCURLY_BRACKET {
+ // Parse the option-data list entry map
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(m);
+} option_data_params RCURLY_BRACKET {
+ /// @todo: the code or name parameters are required.
+ // parsing completed
+};
+
+// This defines parameters specified inside the map that itself
+// is an entry in option-data list. It can either be empty
+// or have a non-empty list of parameters.
+option_data_params: %empty
+ | not_empty_option_data_params
+ ;
+
+// Those parameters can either be a single parameter or
+// a list of parameters separated by comma.
+not_empty_option_data_params: option_data_param
+ | not_empty_option_data_params COMMA option_data_param
+ | not_empty_option_data_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+// Each single option-data parameter can be one of the following
+// expressions.
+option_data_param: option_data_name
+ | option_data_data
+ | option_data_code
+ | option_data_space
+ | option_data_csv_format
+ | option_data_always_send
+ | user_context
+ | comment
+ | unknown_map_entry
+ ;
+
+option_data_name: name;
+
+option_data_data: DATA {
+ ctx.unique("data", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr data(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("data", data);
+ ctx.leave();
+};
+
+option_data_code: code;
+
+option_data_space: space;
+
+option_data_csv_format: CSV_FORMAT COLON BOOLEAN {
+ ctx.unique("csv-format", ctx.loc2pos(@1));
+ ElementPtr space(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("csv-format", space);
+};
+
+option_data_always_send: ALWAYS_SEND COLON BOOLEAN {
+ ctx.unique("always-send", ctx.loc2pos(@1));
+ ElementPtr persist(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("always-send", persist);
+};
+
+// ---- pools ------------------------------------
+
+// This defines the "pools": [ ... ] entry that may appear in subnet4.
+pools_list: POOLS {
+ ctx.unique("pools", ctx.loc2pos(@1));
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("pools", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.POOLS);
+} COLON LSQUARE_BRACKET pools_list_content RSQUARE_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+// Pools may be empty, contain a single pool entry or multiple entries
+// separate by commas.
+pools_list_content: %empty
+ | not_empty_pools_list
+ ;
+
+not_empty_pools_list: pool_list_entry
+ | not_empty_pools_list COMMA pool_list_entry
+ | not_empty_pools_list COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+pool_list_entry: LCURLY_BRACKET {
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+} pool_params RCURLY_BRACKET {
+ // The pool parameter is required.
+ ctx.require("pool", ctx.loc2pos(@1), ctx.loc2pos(@4));
+ ctx.stack_.pop_back();
+};
+
+sub_pool4: LCURLY_BRACKET {
+ // Parse the pool list entry map
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(m);
+} pool_params RCURLY_BRACKET {
+ // The pool parameter is required.
+ ctx.require("pool", ctx.loc2pos(@1), ctx.loc2pos(@4));
+ // parsing completed
+};
+
+pool_params: pool_param
+ | pool_params COMMA pool_param
+ | pool_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+pool_param: pool_entry
+ | option_data_list
+ | client_class
+ | require_client_classes
+ | user_context
+ | comment
+ | unknown_map_entry
+ ;
+
+pool_entry: POOL {
+ ctx.unique("pool", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr pool(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("pool", pool);
+ ctx.leave();
+};
+
+user_context: USER_CONTEXT {
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON map_value {
+ ElementPtr parent = ctx.stack_.back();
+ ElementPtr user_context = $4;
+ ConstElementPtr old = parent->get("user-context");
+
+ // Handle already existing user context
+ if (old) {
+ // Check if it was a comment or a duplicate
+ if ((old->size() != 1) || !old->contains("comment")) {
+ std::stringstream msg;
+ msg << "duplicate user-context entries (previous at "
+ << old->getPosition().str() << ")";
+ error(@1, msg.str());
+ }
+ // Merge the comment
+ user_context->set("comment", old->get("comment"));
+ }
+
+ // Set the user context
+ parent->set("user-context", user_context);
+ ctx.leave();
+};
+
+comment: COMMENT {
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr parent = ctx.stack_.back();
+ ElementPtr user_context(new MapElement(ctx.loc2pos(@1)));
+ ElementPtr comment(new StringElement($4, ctx.loc2pos(@4)));
+ user_context->set("comment", comment);
+
+ // Handle already existing user context
+ ConstElementPtr old = parent->get("user-context");
+ if (old) {
+ // Check for duplicate comment
+ if (old->contains("comment")) {
+ std::stringstream msg;
+ msg << "duplicate user-context/comment entries (previous at "
+ << old->getPosition().str() << ")";
+ error(@1, msg.str());
+ }
+ // Merge the user context in the comment
+ merge(user_context, old);
+ }
+
+ // Set the user context
+ parent->set("user-context", user_context);
+ ctx.leave();
+};
+
+// --- end of pools definition -------------------------------
+
+// --- reservations ------------------------------------------
+reservations: RESERVATIONS {
+ ctx.unique("reservations", ctx.loc2pos(@1));
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("reservations", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.RESERVATIONS);
+} COLON LSQUARE_BRACKET reservations_list RSQUARE_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+reservations_list: %empty
+ | not_empty_reservations_list
+ ;
+
+not_empty_reservations_list: reservation
+ | not_empty_reservations_list COMMA reservation
+ | not_empty_reservations_list COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+reservation: LCURLY_BRACKET {
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+} reservation_params RCURLY_BRACKET {
+ /// @todo: an identifier parameter is required.
+ ctx.stack_.pop_back();
+};
+
+sub_reservation: LCURLY_BRACKET {
+ // Parse the reservations list entry map
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(m);
+} reservation_params RCURLY_BRACKET {
+ /// @todo: an identifier parameter is required.
+ // parsing completed
+};
+
+reservation_params: %empty
+ | not_empty_reservation_params
+ ;
+
+not_empty_reservation_params: reservation_param
+ | not_empty_reservation_params COMMA reservation_param
+ | not_empty_reservation_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+/// @todo probably need to add mac-address as well here
+reservation_param: duid
+ | reservation_client_classes
+ | client_id_value
+ | circuit_id_value
+ | flex_id_value
+ | ip_address
+ | hw_address
+ | hostname
+ | option_data_list
+ | next_server
+ | server_hostname
+ | boot_file_name
+ | user_context
+ | comment
+ | unknown_map_entry
+ ;
+
+next_server: NEXT_SERVER {
+ ctx.unique("next-server", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr next_server(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("next-server", next_server);
+ ctx.leave();
+};
+
+server_hostname: SERVER_HOSTNAME {
+ ctx.unique("server-hostname", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr srv(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("server-hostname", srv);
+ ctx.leave();
+};
+
+boot_file_name: BOOT_FILE_NAME {
+ ctx.unique("boot-file-name", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr bootfile(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("boot-file-name", bootfile);
+ ctx.leave();
+};
+
+ip_address: IP_ADDRESS {
+ ctx.unique("ip-address", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr addr(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("ip-address", addr);
+ ctx.leave();
+};
+
+ip_addresses: IP_ADDRESSES {
+ ctx.unique("ip-addresses", ctx.loc2pos(@1));
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("ip-addresses", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON list_strings {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+duid: DUID {
+ ctx.unique("duid", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr d(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("duid", d);
+ ctx.leave();
+};
+
+hw_address: HW_ADDRESS {
+ ctx.unique("hw-address", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr hw(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("hw-address", hw);
+ ctx.leave();
+};
+
+client_id_value: CLIENT_ID {
+ ctx.unique("client-id", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr hw(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("client-id", hw);
+ ctx.leave();
+};
+
+circuit_id_value: CIRCUIT_ID {
+ ctx.unique("circuit-id", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr hw(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("circuit-id", hw);
+ ctx.leave();
+};
+
+flex_id_value: FLEX_ID {
+ ctx.unique("flex-id", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr hw(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("flex-id", hw);
+ ctx.leave();
+};
+
+hostname: HOSTNAME {
+ ctx.unique("hostname", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr host(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("hostname", host);
+ ctx.leave();
+};
+
+reservation_client_classes: CLIENT_CLASSES {
+ ctx.unique("client-classes", ctx.loc2pos(@1));
+ ElementPtr c(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("client-classes", c);
+ ctx.stack_.push_back(c);
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON list_strings {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+// --- end of reservations definitions -----------------------
+
+// --- relay -------------------------------------------------
+relay: RELAY {
+ ctx.unique("relay", ctx.loc2pos(@1));
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("relay", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.RELAY);
+} COLON LCURLY_BRACKET relay_map RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+relay_map: ip_address
+ | ip_addresses
+ ;
+
+// --- end of relay definitions ------------------------------
+
+// --- client classes ----------------------------------------
+client_classes: CLIENT_CLASSES {
+ ctx.unique("client-classes", ctx.loc2pos(@1));
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("client-classes", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.CLIENT_CLASSES);
+} COLON LSQUARE_BRACKET client_classes_list RSQUARE_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+client_classes_list: client_class_entry
+ | client_classes_list COMMA client_class_entry
+ | client_classes_list COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+client_class_entry: LCURLY_BRACKET {
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+} client_class_params RCURLY_BRACKET {
+ // The name client class parameter is required.
+ ctx.require("name", ctx.loc2pos(@1), ctx.loc2pos(@4));
+ ctx.stack_.pop_back();
+};
+
+client_class_params: %empty
+ | not_empty_client_class_params
+ ;
+
+not_empty_client_class_params: client_class_param
+ | not_empty_client_class_params COMMA client_class_param
+ | not_empty_client_class_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+client_class_param: client_class_name
+ | client_class_test
+ | only_if_required
+ | option_def_list
+ | option_data_list
+ | next_server
+ | server_hostname
+ | boot_file_name
+ | user_context
+ | comment
+ | unknown_map_entry
+ | valid_lifetime
+ | min_valid_lifetime
+ | max_valid_lifetime
+ ;
+
+client_class_name: name;
+
+client_class_test: TEST {
+ ctx.unique("test", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr test(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("test", test);
+ ctx.leave();
+};
+
+only_if_required: ONLY_IF_REQUIRED COLON BOOLEAN {
+ ctx.unique("only-if-required", ctx.loc2pos(@1));
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("only-if-required", b);
+};
+
+// --- end of client classes ---------------------------------
+
+dhcp4o6_port: DHCP4O6_PORT COLON INTEGER {
+ ctx.unique("dhcp4o6-port", ctx.loc2pos(@1));
+ ElementPtr time(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("dhcp4o6-port", time);
+};
+
+// --- control socket ----------------------------------------
+
+control_socket: CONTROL_SOCKET {
+ ctx.unique("control-socket", ctx.loc2pos(@1));
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("control-socket", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.CONTROL_SOCKET);
+} COLON LCURLY_BRACKET control_socket_params RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+control_socket_params: control_socket_param
+ | control_socket_params COMMA control_socket_param
+ | control_socket_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+control_socket_param: control_socket_type
+ | control_socket_name
+ | user_context
+ | comment
+ | unknown_map_entry
+ ;
+
+control_socket_type: SOCKET_TYPE {
+ ctx.unique("socket-type", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr stype(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("socket-type", stype);
+ ctx.leave();
+};
+
+control_socket_name: SOCKET_NAME {
+ ctx.unique("socket-name", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr name(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("socket-name", name);
+ ctx.leave();
+};
+
+
+// --- dhcp-queue-control ---------------------------------------------
+
+dhcp_queue_control: DHCP_QUEUE_CONTROL {
+ ctx.unique("dhcp-queue-control", ctx.loc2pos(@1));
+ ElementPtr qc(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("dhcp-queue-control", qc);
+ ctx.stack_.push_back(qc);
+ ctx.enter(ctx.DHCP_QUEUE_CONTROL);
+} COLON LCURLY_BRACKET queue_control_params RCURLY_BRACKET {
+ // The enable queue parameter is required.
+ ctx.require("enable-queue", ctx.loc2pos(@4), ctx.loc2pos(@6));
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+queue_control_params: queue_control_param
+ | queue_control_params COMMA queue_control_param
+ | queue_control_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+queue_control_param: enable_queue
+ | queue_type
+ | capacity
+ | user_context
+ | comment
+ | arbitrary_map_entry
+ ;
+
+enable_queue: ENABLE_QUEUE COLON BOOLEAN {
+ ctx.unique("enable-queue", ctx.loc2pos(@1));
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("enable-queue", b);
+};
+
+queue_type: QUEUE_TYPE {
+ ctx.unique("queue-type", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr qt(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("queue-type", qt);
+ ctx.leave();
+};
+
+capacity: CAPACITY COLON INTEGER {
+ ctx.unique("capacity", ctx.loc2pos(@1));
+ ElementPtr c(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("capacity", c);
+};
+
+arbitrary_map_entry: STRING {
+ ctx.unique($1, ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON value {
+ ctx.stack_.back()->set($1, $4);
+ ctx.leave();
+};
+
+// --- dhcp ddns ---------------------------------------------
+
+dhcp_ddns: DHCP_DDNS {
+ ctx.unique("dhcp-ddns", ctx.loc2pos(@1));
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("dhcp-ddns", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.DHCP_DDNS);
+} COLON LCURLY_BRACKET dhcp_ddns_params RCURLY_BRACKET {
+ // The enable updates DHCP DDNS parameter is required.
+ ctx.require("enable-updates", ctx.loc2pos(@4), ctx.loc2pos(@6));
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+sub_dhcp_ddns: LCURLY_BRACKET {
+ // Parse the dhcp-ddns map
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(m);
+} dhcp_ddns_params RCURLY_BRACKET {
+ // The enable updates DHCP DDNS parameter is required.
+ ctx.require("enable-updates", ctx.loc2pos(@1), ctx.loc2pos(@4));
+ // parsing completed
+};
+
+dhcp_ddns_params: dhcp_ddns_param
+ | dhcp_ddns_params COMMA dhcp_ddns_param
+ | dhcp_ddns_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+dhcp_ddns_param: enable_updates
+ | server_ip
+ | server_port
+ | sender_ip
+ | sender_port
+ | max_queue_size
+ | ncr_protocol
+ | ncr_format
+ | dep_override_no_update
+ | dep_override_client_update
+ | dep_replace_client_name
+ | dep_generated_prefix
+ | dep_qualifying_suffix
+ | dep_hostname_char_set
+ | dep_hostname_char_replacement
+ | user_context
+ | comment
+ | unknown_map_entry
+ ;
+
+enable_updates: ENABLE_UPDATES COLON BOOLEAN {
+ ctx.unique("enable-updates", ctx.loc2pos(@1));
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("enable-updates", b);
+};
+
+server_ip: SERVER_IP {
+ ctx.unique("server-ip", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr s(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("server-ip", s);
+ ctx.leave();
+};
+
+server_port: SERVER_PORT COLON INTEGER {
+ ctx.unique("server-port", ctx.loc2pos(@1));
+ ElementPtr i(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("server-port", i);
+};
+
+sender_ip: SENDER_IP {
+ ctx.unique("sender-ip", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr s(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("sender-ip", s);
+ ctx.leave();
+};
+
+sender_port: SENDER_PORT COLON INTEGER {
+ ctx.unique("sender-port", ctx.loc2pos(@1));
+ ElementPtr i(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("sender-port", i);
+};
+
+max_queue_size: MAX_QUEUE_SIZE COLON INTEGER {
+ ctx.unique("max-queue-size", ctx.loc2pos(@1));
+ ElementPtr i(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("max-queue-size", i);
+};
+
+ncr_protocol: NCR_PROTOCOL {
+ ctx.unique("ncr-protocol", ctx.loc2pos(@1));
+ ctx.enter(ctx.NCR_PROTOCOL);
+} COLON ncr_protocol_value {
+ ctx.stack_.back()->set("ncr-protocol", $4);
+ ctx.leave();
+};
+
+ncr_protocol_value:
+ UDP { $$ = ElementPtr(new StringElement("UDP", ctx.loc2pos(@1))); }
+ | TCP { $$ = ElementPtr(new StringElement("TCP", ctx.loc2pos(@1))); }
+ ;
+
+ncr_format: NCR_FORMAT {
+ ctx.unique("ncr-format", ctx.loc2pos(@1));
+ ctx.enter(ctx.NCR_FORMAT);
+} COLON JSON {
+ ElementPtr json(new StringElement("JSON", ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("ncr-format", json);
+ ctx.leave();
+};
+
+// Deprecated, moved to global/network scopes. Eventually it should be removed.
+dep_qualifying_suffix: QUALIFYING_SUFFIX {
+ ctx.unique("qualifying-suffix", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr s(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("qualifying-suffix", s);
+ ctx.leave();
+};
+
+// Deprecated, moved to global/network scopes. Eventually it should be removed.
+dep_override_no_update: OVERRIDE_NO_UPDATE COLON BOOLEAN {
+ ctx.unique("override-no-update", ctx.loc2pos(@1));
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("override-no-update", b);
+};
+
+// Deprecated, moved to global/network scopes. Eventually it should be removed.
+dep_override_client_update: OVERRIDE_CLIENT_UPDATE COLON BOOLEAN {
+ ctx.unique("override-client-update", ctx.loc2pos(@1));
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("override-client-update", b);
+};
+
+// Deprecated, moved to global/network scopes. Eventually it should be removed.
+dep_replace_client_name: REPLACE_CLIENT_NAME {
+ ctx.unique("replace-client-name", ctx.loc2pos(@1));
+ ctx.enter(ctx.REPLACE_CLIENT_NAME);
+} COLON ddns_replace_client_name_value {
+ ctx.stack_.back()->set("replace-client-name", $4);
+ ctx.leave();
+};
+
+// Deprecated, moved to global/network scopes. Eventually it should be removed.
+dep_generated_prefix: GENERATED_PREFIX {
+ ctx.unique("generated-prefix", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr s(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("generated-prefix", s);
+ ctx.leave();
+};
+
+// Deprecated, moved to global/network scopes. Eventually it should be removed.
+dep_hostname_char_set: HOSTNAME_CHAR_SET {
+ ctx.unique("hostname-char-set", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr s(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("hostname-char-set", s);
+ ctx.leave();
+};
+
+// Deprecated, moved to global/network scopes. Eventually it should be removed.
+dep_hostname_char_replacement: HOSTNAME_CHAR_REPLACEMENT {
+ ctx.unique("hostname-char-replacement", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr s(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("hostname-char-replacement", s);
+ ctx.leave();
+};
+
+
+// Config control information element
+
+config_control: CONFIG_CONTROL {
+ ctx.unique("config-control", ctx.loc2pos(@1));
+ ElementPtr i(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("config-control", i);
+ ctx.stack_.push_back(i);
+ ctx.enter(ctx.CONFIG_CONTROL);
+} COLON LCURLY_BRACKET config_control_params RCURLY_BRACKET {
+ // No config control params are required
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+sub_config_control: LCURLY_BRACKET {
+ // Parse the config-control map
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(m);
+} config_control_params RCURLY_BRACKET {
+ // No config_control params are required
+ // parsing completed
+};
+
+// This defines that subnet can have one or more parameters.
+config_control_params: config_control_param
+ | config_control_params COMMA config_control_param
+ | config_control_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+// This defines a list of allowed parameters for each subnet.
+config_control_param: config_databases
+ | config_fetch_wait_time
+ ;
+
+config_databases: CONFIG_DATABASES {
+ ctx.unique("config-databases", ctx.loc2pos(@1));
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("config-databases", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.CONFIG_DATABASE);
+} COLON LSQUARE_BRACKET database_list RSQUARE_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+config_fetch_wait_time: CONFIG_FETCH_WAIT_TIME COLON INTEGER {
+ ctx.unique("config-fetch-wait-time", ctx.loc2pos(@1));
+ ElementPtr value(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("config-fetch-wait-time", value);
+};
+
+// --- loggers entry -----------------------------------------
+
+loggers: LOGGERS {
+ ctx.unique("loggers", ctx.loc2pos(@1));
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("loggers", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.LOGGERS);
+} COLON LSQUARE_BRACKET loggers_entries RSQUARE_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+// These are the parameters allowed in loggers: either one logger
+// entry or multiple entries separate by commas.
+loggers_entries: logger_entry
+ | loggers_entries COMMA logger_entry
+ | loggers_entries COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+// This defines a single entry defined in loggers.
+logger_entry: LCURLY_BRACKET {
+ ElementPtr l(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(l);
+ ctx.stack_.push_back(l);
+} logger_params RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+};
+
+logger_params: logger_param
+ | logger_params COMMA logger_param
+ | logger_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+logger_param: name
+ | output_options_list
+ | debuglevel
+ | severity
+ | user_context
+ | comment
+ | unknown_map_entry
+ ;
+
+debuglevel: DEBUGLEVEL COLON INTEGER {
+ ctx.unique("debuglevel", ctx.loc2pos(@1));
+ ElementPtr dl(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("debuglevel", dl);
+};
+
+severity: SEVERITY {
+ ctx.unique("severity", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr sev(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("severity", sev);
+ ctx.leave();
+};
+
+output_options_list: OUTPUT_OPTIONS {
+ ctx.unique("output_options", ctx.loc2pos(@1));
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("output_options", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.OUTPUT_OPTIONS);
+} COLON LSQUARE_BRACKET output_options_list_content RSQUARE_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+output_options_list_content: output_entry
+ | output_options_list_content COMMA output_entry
+ | output_options_list_content COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+output_entry: LCURLY_BRACKET {
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+} output_params_list RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+};
+
+output_params_list: output_params
+ | output_params_list COMMA output_params
+ | output_params_list COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+output_params: output
+ | flush
+ | maxsize
+ | maxver
+ | pattern
+ ;
+
+output: OUTPUT {
+ ctx.unique("output", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr sev(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("output", sev);
+ ctx.leave();
+};
+
+flush: FLUSH COLON BOOLEAN {
+ ctx.unique("flush", ctx.loc2pos(@1));
+ ElementPtr flush(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("flush", flush);
+};
+
+maxsize: MAXSIZE COLON INTEGER {
+ ctx.unique("maxsize", ctx.loc2pos(@1));
+ ElementPtr maxsize(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("maxsize", maxsize);
+};
+
+maxver: MAXVER COLON INTEGER {
+ ctx.unique("maxver", ctx.loc2pos(@1));
+ ElementPtr maxver(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("maxver", maxver);
+};
+
+pattern: PATTERN {
+ ctx.unique("pattern", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr sev(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("pattern", sev);
+ ctx.leave();
+};
+
+compatibility: COMPATIBILITY {
+ ctx.unique("compatibility", ctx.loc2pos(@1));
+ ElementPtr i(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("compatibility", i);
+ ctx.stack_.push_back(i);
+ ctx.enter(ctx.COMPATIBILITY);
+} COLON LCURLY_BRACKET compatibility_params RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+compatibility_params: compatibility_param
+ | compatibility_params COMMA compatibility_param
+ | compatibility_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+compatibility_param: lenient_option_parsing
+ | unknown_map_entry
+ ;
+
+lenient_option_parsing: LENIENT_OPTION_PARSING COLON BOOLEAN {
+ ctx.unique("lenient-option-parsing", ctx.loc2pos(@1));
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("lenient-option-parsing", b);
+};
+
+%%
+
+void
+isc::dhcp::Dhcp4Parser::error(const location_type& loc,
+ const std::string& what)
+{
+ ctx.error(loc, what);
+}
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
new file mode 100644
index 0000000..263cb87
--- /dev/null
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -0,0 +1,4351 @@
+// Copyright (C) 2011-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 <kea_version.h>
+
+#include <dhcp/dhcp4.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/option_string.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt4o6.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp4/client_handler.h>
+#include <dhcp4/dhcp4to6_ipc.h>
+#include <dhcp4/dhcp4_log.h>
+#include <dhcp4/dhcp4_srv.h>
+#include <asiolink/addr_utilities.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_host_operations.h>
+#include <dhcpsrv/cfg_iface.h>
+#include <dhcpsrv/cfg_shared_networks.h>
+#include <dhcpsrv/cfg_subnets4.h>
+#include <dhcpsrv/fuzz.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/ncr_generator.h>
+#include <dhcpsrv/shared_network.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_selector.h>
+#include <dhcpsrv/utils.h>
+#include <eval/evaluate.h>
+#include <eval/eval_messages.h>
+#include <hooks/callout_handle.h>
+#include <hooks/hooks_log.h>
+#include <hooks/hooks_manager.h>
+#include <stats/stats_mgr.h>
+#include <util/strutil.h>
+#include <log/logger.h>
+#include <cryptolink/cryptolink.h>
+#include <cfgrpt/config_report.h>
+
+#ifdef HAVE_MYSQL
+#include <dhcpsrv/mysql_lease_mgr.h>
+#endif
+#ifdef HAVE_PGSQL
+#include <dhcpsrv/pgsql_lease_mgr.h>
+#endif
+#include <dhcpsrv/memfile_lease_mgr.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <boost/pointer_cast.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <functional>
+#include <iomanip>
+#include <set>
+#include <cstdlib>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::cryptolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp_ddns;
+using namespace isc::hooks;
+using namespace isc::log;
+using namespace isc::stats;
+using namespace isc::util;
+using namespace std;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// Structure that holds registered hook indexes
+struct Dhcp4Hooks {
+ int hook_index_buffer4_receive_; ///< index for "buffer4_receive" hook point
+ int hook_index_pkt4_receive_; ///< index for "pkt4_receive" hook point
+ int hook_index_subnet4_select_; ///< index for "subnet4_select" hook point
+ int hook_index_leases4_committed_; ///< index for "leases4_committed" hook point
+ int hook_index_lease4_release_; ///< index for "lease4_release" hook point
+ int hook_index_pkt4_send_; ///< index for "pkt4_send" hook point
+ int hook_index_buffer4_send_; ///< index for "buffer4_send" hook point
+ int hook_index_lease4_decline_; ///< index for "lease4_decline" hook point
+ int hook_index_host4_identifier_; ///< index for "host4_identifier" hook point
+ int hook_index_ddns4_update_; ///< index for "ddns4_update" hook point
+
+ /// Constructor that registers hook points for DHCPv4 engine
+ Dhcp4Hooks() {
+ hook_index_buffer4_receive_ = HooksManager::registerHook("buffer4_receive");
+ hook_index_pkt4_receive_ = HooksManager::registerHook("pkt4_receive");
+ hook_index_subnet4_select_ = HooksManager::registerHook("subnet4_select");
+ hook_index_leases4_committed_ = HooksManager::registerHook("leases4_committed");
+ hook_index_lease4_release_ = HooksManager::registerHook("lease4_release");
+ hook_index_pkt4_send_ = HooksManager::registerHook("pkt4_send");
+ hook_index_buffer4_send_ = HooksManager::registerHook("buffer4_send");
+ hook_index_lease4_decline_ = HooksManager::registerHook("lease4_decline");
+ hook_index_host4_identifier_ = HooksManager::registerHook("host4_identifier");
+ hook_index_ddns4_update_ = HooksManager::registerHook("ddns4_update");
+ }
+};
+
+/// List of statistics which is initialized to 0 during the DHCPv4
+/// server startup.
+std::set<std::string> dhcp4_statistics = {
+ "pkt4-received",
+ "pkt4-discover-received",
+ "pkt4-offer-received",
+ "pkt4-request-received",
+ "pkt4-ack-received",
+ "pkt4-nak-received",
+ "pkt4-release-received",
+ "pkt4-decline-received",
+ "pkt4-inform-received",
+ "pkt4-unknown-received",
+ "pkt4-sent",
+ "pkt4-offer-sent",
+ "pkt4-ack-sent",
+ "pkt4-nak-sent",
+ "pkt4-parse-failed",
+ "pkt4-receive-drop",
+ "v4-allocation-fail",
+ "v4-allocation-fail-shared-network",
+ "v4-allocation-fail-subnet",
+ "v4-allocation-fail-no-pools",
+ "v4-allocation-fail-classes",
+ "v4-reservation-conflicts"
+};
+
+} // end of anonymous namespace
+
+// Declare a Hooks object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+Dhcp4Hooks Hooks;
+
+namespace isc {
+namespace dhcp {
+
+Dhcpv4Exchange::Dhcpv4Exchange(const AllocEnginePtr& alloc_engine,
+ const Pkt4Ptr& query,
+ AllocEngine::ClientContext4Ptr& context,
+ const Subnet4Ptr& subnet,
+ bool& drop)
+ : alloc_engine_(alloc_engine), query_(query), resp_(),
+ context_(context) {
+
+ if (!alloc_engine_) {
+ isc_throw(BadValue, "alloc_engine value must not be NULL"
+ " when creating an instance of the Dhcpv4Exchange");
+ }
+
+ if (!query_) {
+ isc_throw(BadValue, "query value must not be NULL when"
+ " creating an instance of the Dhcpv4Exchange");
+ }
+
+ // Reset the given context argument.
+ context.reset();
+
+ // Create response message.
+ initResponse();
+ // Select subnet for the query message.
+ context_->subnet_ = subnet;
+
+ // If subnet found, retrieve client identifier which will be needed
+ // for allocations and search for reservations associated with a
+ // subnet/shared network.
+ SharedNetwork4Ptr sn;
+ if (subnet && !context_->early_global_reservations_lookup_) {
+ OptionPtr opt_clientid = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ if (opt_clientid) {
+ context_->clientid_.reset(new ClientId(opt_clientid->getData()));
+ }
+ }
+
+ if (subnet) {
+ // Find static reservations if not disabled for our subnet.
+ if (subnet->getReservationsInSubnet() ||
+ subnet->getReservationsGlobal()) {
+ // Before we can check for static reservations, we need to prepare a set
+ // of identifiers to be used for this.
+ if (!context_->early_global_reservations_lookup_) {
+ setHostIdentifiers(context_);
+ }
+
+ // Check for static reservations.
+ alloc_engine->findReservation(*context_);
+
+ // Get shared network to see if it is set for a subnet.
+ subnet->getSharedNetwork(sn);
+ }
+ }
+
+ // Global host reservations are independent of a selected subnet. If the
+ // global reservations contain client classes we should use them in case
+ // they are meant to affect pool selection. Also, if the subnet does not
+ // belong to a shared network we can use the reserved client classes
+ // because there is no way our subnet could change. Such classes may
+ // affect selection of a pool within the selected subnet.
+ auto global_host = context_->globalHost();
+ auto current_host = context_->currentHost();
+ if ((!context_->early_global_reservations_lookup_ &&
+ global_host && !global_host->getClientClasses4().empty()) ||
+ (!sn && current_host && !current_host->getClientClasses4().empty())) {
+ // We have already evaluated client classes and some of them may
+ // be in conflict with the reserved classes. Suppose there are
+ // two classes defined in the server configuration: first_class
+ // and second_class and the test for the second_class it looks
+ // like this: "not member('first_class')". If the first_class
+ // initially evaluates to false, the second_class evaluates to
+ // true. If the first_class is now set within the hosts reservations
+ // and we don't remove the previously evaluated second_class we'd
+ // end up with both first_class and second_class evaluated to
+ // true. In order to avoid that, we have to remove the classes
+ // evaluated in the first pass and evaluate them again. As
+ // a result, the first_class set via the host reservation will
+ // replace the second_class because the second_class will this
+ // time evaluate to false as desired.
+ removeDependentEvaluatedClasses(query);
+ setReservedClientClasses(context_);
+ evaluateClasses(query, false);
+ }
+
+ // Set KNOWN builtin class if something was found, UNKNOWN if not.
+ if (!context_->hosts_.empty()) {
+ query->addClass("KNOWN");
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_ASSIGNED)
+ .arg(query->getLabel())
+ .arg("KNOWN");
+ } else {
+ query->addClass("UNKNOWN");
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_ASSIGNED)
+ .arg(query->getLabel())
+ .arg("UNKNOWN");
+ }
+
+ // Perform second pass of classification.
+ evaluateClasses(query, true);
+
+ const ClientClasses& classes = query_->getClasses();
+ if (!classes.empty()) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_ASSIGNED)
+ .arg(query_->getLabel())
+ .arg(classes.toText());
+ }
+
+ // Check the DROP special class.
+ if (query_->inClass("DROP")) {
+ LOG_DEBUG(packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0013)
+ .arg(query_->toText());
+ isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
+ static_cast<int64_t>(1));
+ drop = true;
+ }
+}
+
+void
+Dhcpv4Exchange::initResponse() {
+ uint8_t resp_type = 0;
+ switch (getQuery()->getType()) {
+ case DHCPDISCOVER:
+ resp_type = DHCPOFFER;
+ break;
+ case DHCPREQUEST:
+ case DHCPINFORM:
+ resp_type = DHCPACK;
+ break;
+ default:
+ ;
+ }
+ // Only create a response if one is required.
+ if (resp_type > 0) {
+ resp_.reset(new Pkt4(resp_type, getQuery()->getTransid()));
+ copyDefaultFields();
+ copyDefaultOptions();
+
+ if (getQuery()->isDhcp4o6()) {
+ initResponse4o6();
+ }
+ }
+}
+
+void
+Dhcpv4Exchange::initResponse4o6() {
+ Pkt4o6Ptr query = boost::dynamic_pointer_cast<Pkt4o6>(getQuery());
+ if (!query) {
+ return;
+ }
+ const Pkt6Ptr& query6 = query->getPkt6();
+ Pkt6Ptr resp6(new Pkt6(DHCPV6_DHCPV4_RESPONSE, query6->getTransid()));
+ // Don't add client-id or server-id
+ // But copy relay info
+ if (!query6->relay_info_.empty()) {
+ resp6->copyRelayInfo(query6);
+ }
+ // Copy interface, and remote address and port
+ resp6->setIface(query6->getIface());
+ resp6->setIndex(query6->getIndex());
+ resp6->setRemoteAddr(query6->getRemoteAddr());
+ resp6->setRemotePort(query6->getRemotePort());
+ resp_.reset(new Pkt4o6(resp_, resp6));
+}
+
+void
+Dhcpv4Exchange::copyDefaultFields() {
+ resp_->setIface(query_->getIface());
+ resp_->setIndex(query_->getIndex());
+
+ // explicitly set this to 0
+ resp_->setSiaddr(IOAddress::IPV4_ZERO_ADDRESS());
+ // ciaddr is always 0, except for the Renew/Rebind state and for
+ // Inform when it may be set to the ciaddr sent by the client.
+ if (query_->getType() == DHCPINFORM) {
+ resp_->setCiaddr(query_->getCiaddr());
+ } else {
+ resp_->setCiaddr(IOAddress::IPV4_ZERO_ADDRESS());
+ }
+ resp_->setHops(query_->getHops());
+
+ // copy MAC address
+ resp_->setHWAddr(query_->getHWAddr());
+
+ // relay address
+ resp_->setGiaddr(query_->getGiaddr());
+
+ // If src/dest HW addresses are used by the packet filtering class
+ // we need to copy them as well. There is a need to check that the
+ // address being set is not-NULL because an attempt to set the NULL
+ // HW would result in exception. If these values are not set, the
+ // the default HW addresses (zeroed) should be generated by the
+ // packet filtering class when creating Ethernet header for
+ // outgoing packet.
+ HWAddrPtr src_hw_addr = query_->getLocalHWAddr();
+ if (src_hw_addr) {
+ resp_->setLocalHWAddr(src_hw_addr);
+ }
+ HWAddrPtr dst_hw_addr = query_->getRemoteHWAddr();
+ if (dst_hw_addr) {
+ resp_->setRemoteHWAddr(dst_hw_addr);
+ }
+
+ // Copy flags from the request to the response per RFC 2131
+ resp_->setFlags(query_->getFlags());
+}
+
+void
+Dhcpv4Exchange::copyDefaultOptions() {
+ // Let's copy client-id to response. See RFC6842.
+ // It is possible to disable RFC6842 to keep backward compatibility
+ bool echo = CfgMgr::instance().getCurrentCfg()->getEchoClientId();
+ OptionPtr client_id = query_->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ if (client_id && echo) {
+ resp_->addOption(client_id);
+ }
+
+ // If this packet is relayed, we want to copy Relay Agent Info option
+ // when it is not empty.
+ OptionPtr rai = query_->getOption(DHO_DHCP_AGENT_OPTIONS);
+ if (rai && (rai->len() > Option::OPTION4_HDR_LEN)) {
+ resp_->addOption(rai);
+ }
+
+ // RFC 3011 states about the Subnet Selection Option
+
+ // "Servers configured to support this option MUST return an
+ // identical copy of the option to any client that sends it,
+ // regardless of whether or not the client requests the option in
+ // a parameter request list. Clients using this option MUST
+ // discard DHCPOFFER or DHCPACK packets that do not contain this
+ // option."
+ OptionPtr subnet_sel = query_->getOption(DHO_SUBNET_SELECTION);
+ if (subnet_sel) {
+ resp_->addOption(subnet_sel);
+ }
+}
+
+void
+Dhcpv4Exchange::setHostIdentifiers(AllocEngine::ClientContext4Ptr context) {
+ const ConstCfgHostOperationsPtr cfg =
+ CfgMgr::instance().getCurrentCfg()->getCfgHostOperations4();
+
+ // Collect host identifiers. The identifiers are stored in order of preference.
+ // The server will use them in that order to search for host reservations.
+ BOOST_FOREACH(const Host::IdentifierType& id_type,
+ cfg->getIdentifierTypes()) {
+ switch (id_type) {
+ case Host::IDENT_HWADDR:
+ if (context->hwaddr_ && !context->hwaddr_->hwaddr_.empty()) {
+ context->addHostIdentifier(id_type, context->hwaddr_->hwaddr_);
+ }
+ break;
+
+ case Host::IDENT_DUID:
+ if (context->clientid_) {
+ const std::vector<uint8_t>& vec = context->clientid_->getDuid();
+ if (!vec.empty()) {
+ // Client identifier type = DUID? Client identifier holding a DUID
+ // comprises Type (1 byte), IAID (4 bytes), followed by the actual
+ // DUID. Thus, the minimal length is 6.
+ if ((vec[0] == CLIENT_ID_OPTION_TYPE_DUID) && (vec.size() > 5)) {
+ // Extract DUID, skip IAID.
+ context->addHostIdentifier(id_type,
+ std::vector<uint8_t>(vec.begin() + 5,
+ vec.end()));
+ }
+ }
+ }
+ break;
+
+ case Host::IDENT_CIRCUIT_ID:
+ {
+ OptionPtr rai = context->query_->getOption(DHO_DHCP_AGENT_OPTIONS);
+ if (rai) {
+ OptionPtr circuit_id_opt = rai->getOption(RAI_OPTION_AGENT_CIRCUIT_ID);
+ if (circuit_id_opt) {
+ const OptionBuffer& circuit_id_vec = circuit_id_opt->getData();
+ if (!circuit_id_vec.empty()) {
+ context->addHostIdentifier(id_type, circuit_id_vec);
+ }
+ }
+ }
+ }
+ break;
+
+ case Host::IDENT_CLIENT_ID:
+ if (context->clientid_) {
+ const std::vector<uint8_t>& vec = context->clientid_->getDuid();
+ if (!vec.empty()) {
+ context->addHostIdentifier(id_type, vec);
+ }
+ }
+ break;
+ case Host::IDENT_FLEX:
+ {
+ if (!HooksManager::calloutsPresent(Hooks.hook_index_host4_identifier_)) {
+ break;
+ }
+
+ CalloutHandlePtr callout_handle = getCalloutHandle(context->query_);
+
+ Host::IdentifierType type = Host::IDENT_FLEX;
+ std::vector<uint8_t> id;
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+ // Pass incoming packet as argument
+ callout_handle->setArgument("query4", context->query_);
+ callout_handle->setArgument("id_type", type);
+ callout_handle->setArgument("id_value", id);
+
+ // Call callouts
+ HooksManager::callCallouts(Hooks.hook_index_host4_identifier_,
+ *callout_handle);
+
+ callout_handle->getArgument("id_type", type);
+ callout_handle->getArgument("id_value", id);
+
+ if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_CONTINUE) &&
+ !id.empty()) {
+
+ LOG_DEBUG(packet4_logger, DBGLVL_TRACE_BASIC, DHCP4_FLEX_ID)
+ .arg(Host::getIdentifierAsText(type, &id[0], id.size()));
+
+ context->addHostIdentifier(type, id);
+ }
+ break;
+ }
+ default:
+ ;
+ }
+ }
+}
+
+void
+Dhcpv4Exchange::removeDependentEvaluatedClasses(const Pkt4Ptr& query) {
+ const ClientClassDictionaryPtr& dict =
+ CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
+ const ClientClassDefListPtr& defs_ptr = dict->getClasses();
+ for (auto def : *defs_ptr) {
+ // Only remove evaluated classes. Other classes can be
+ // assigned via hooks libraries and we should not remove
+ // them because there is no way they can be added back.
+ if (def->getMatchExpr()) {
+ query->classes_.erase(def->getName());
+ }
+ }
+}
+
+void
+Dhcpv4Exchange::setReservedClientClasses(AllocEngine::ClientContext4Ptr context) {
+ if (context->currentHost() && context->query_) {
+ const ClientClasses& classes = context->currentHost()->getClientClasses4();
+ for (ClientClasses::const_iterator cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ context->query_->addClass(*cclass);
+ }
+ }
+}
+
+void
+Dhcpv4Exchange::conditionallySetReservedClientClasses() {
+ if (context_->subnet_) {
+ SharedNetwork4Ptr shared_network;
+ context_->subnet_->getSharedNetwork(shared_network);
+ if (shared_network) {
+ ConstHostPtr host = context_->currentHost();
+ if (host && (host->getIPv4SubnetID() != SUBNET_ID_GLOBAL)) {
+ setReservedClientClasses(context_);
+ }
+ }
+ }
+}
+
+void
+Dhcpv4Exchange::setReservedMessageFields() {
+ ConstHostPtr host = context_->currentHost();
+ // Nothing to do if host reservations not specified for this client.
+ if (host) {
+ if (!host->getNextServer().isV4Zero()) {
+ resp_->setSiaddr(host->getNextServer());
+ }
+
+ std::string sname = host->getServerHostname();
+ if (!sname.empty()) {
+ resp_->setSname(reinterpret_cast<const uint8_t*>(sname.c_str()),
+ sname.size());
+ }
+
+ std::string bootfile = host->getBootFileName();
+ if (!bootfile.empty()) {
+ resp_->setFile(reinterpret_cast<const uint8_t*>(bootfile.c_str()),
+ bootfile.size());
+ }
+ }
+}
+
+void Dhcpv4Exchange::classifyByVendor(const Pkt4Ptr& pkt) {
+ // Built-in vendor class processing
+ boost::shared_ptr<OptionString> vendor_class =
+ boost::dynamic_pointer_cast<OptionString>(pkt->getOption(DHO_VENDOR_CLASS_IDENTIFIER));
+
+ if (!vendor_class) {
+ return;
+ }
+
+ pkt->addClass(Dhcpv4Srv::VENDOR_CLASS_PREFIX + vendor_class->getValue());
+}
+
+void Dhcpv4Exchange::classifyPacket(const Pkt4Ptr& pkt) {
+ // All packets belongs to ALL.
+ pkt->addClass("ALL");
+
+ // First: built-in vendor class processing.
+ classifyByVendor(pkt);
+
+ // Run match expressions on classes not depending on KNOWN/UNKNOWN.
+ evaluateClasses(pkt, false);
+}
+
+void Dhcpv4Exchange::evaluateClasses(const Pkt4Ptr& pkt, bool depend_on_known) {
+ // Note getClientClassDictionary() cannot be null
+ const ClientClassDictionaryPtr& dict =
+ CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
+ const ClientClassDefListPtr& defs_ptr = dict->getClasses();
+ for (ClientClassDefList::const_iterator it = defs_ptr->cbegin();
+ it != defs_ptr->cend(); ++it) {
+ // Note second cannot be null
+ const ExpressionPtr& expr_ptr = (*it)->getMatchExpr();
+ // Nothing to do without an expression to evaluate
+ if (!expr_ptr) {
+ continue;
+ }
+ // Not the right time if only when required
+ if ((*it)->getRequired()) {
+ continue;
+ }
+ // Not the right pass.
+ if ((*it)->getDependOnKnown() != depend_on_known) {
+ continue;
+ }
+ // Evaluate the expression which can return false (no match),
+ // true (match) or raise an exception (error)
+ try {
+ bool status = evaluateBool(*expr_ptr, *pkt);
+ if (status) {
+ LOG_INFO(options4_logger, EVAL_RESULT)
+ .arg((*it)->getName())
+ .arg(status);
+ // Matching: add the class
+ pkt->addClass((*it)->getName());
+ } else {
+ LOG_DEBUG(options4_logger, DBG_DHCP4_DETAIL, EVAL_RESULT)
+ .arg((*it)->getName())
+ .arg(status);
+ }
+ } catch (const Exception& ex) {
+ LOG_ERROR(options4_logger, EVAL_RESULT)
+ .arg((*it)->getName())
+ .arg(ex.what());
+ } catch (...) {
+ LOG_ERROR(options4_logger, EVAL_RESULT)
+ .arg((*it)->getName())
+ .arg("get exception?");
+ }
+ }
+}
+
+const std::string Dhcpv4Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
+
+Dhcpv4Srv::Dhcpv4Srv(uint16_t server_port, uint16_t client_port,
+ const bool use_bcast, const bool direct_response_desired)
+ : io_service_(new IOService()), server_port_(server_port),
+ client_port_(client_port), shutdown_(true),
+ alloc_engine_(), use_bcast_(use_bcast),
+ network_state_(new NetworkState(NetworkState::DHCPv4)),
+ cb_control_(new CBControlDHCPv4()),
+ test_send_responses_to_source_(false) {
+
+ const char* env = std::getenv("KEA_TEST_SEND_RESPONSES_TO_SOURCE");
+ if (env) {
+ LOG_WARN(dhcp4_logger, DHCP4_TESTING_MODE_SEND_TO_SOURCE_ENABLED);
+ test_send_responses_to_source_ = true;
+ }
+
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET)
+ .arg(server_port);
+
+ try {
+ // Port 0 is used for testing purposes where we don't open broadcast
+ // capable sockets. So, set the packet filter handling direct traffic
+ // only if we are in non-test mode.
+ if (server_port) {
+ // First call to instance() will create IfaceMgr (it's a singleton)
+ // it may throw something if things go wrong.
+ // The 'true' value of the call to setMatchingPacketFilter imposes
+ // that IfaceMgr will try to use the mechanism to respond directly
+ // to the client which doesn't have address assigned. This capability
+ // may be lacking on some OSes, so there is no guarantee that server
+ // will be able to respond directly.
+ IfaceMgr::instance().setMatchingPacketFilter(direct_response_desired);
+ }
+
+ // Instantiate allocation engine. The number of allocation attempts equal
+ // to zero indicates that the allocation engine will use the number of
+ // attempts depending on the pool size.
+ alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 0,
+ false /* false = IPv4 */));
+
+ /// @todo call loadLibraries() when handling configuration changes
+
+ } catch (const std::exception &e) {
+ LOG_ERROR(dhcp4_logger, DHCP4_SRV_CONSTRUCT_ERROR).arg(e.what());
+ shutdown_ = true;
+ return;
+ }
+
+ // Initializing all observations with default value
+ setPacketStatisticsDefaults();
+ shutdown_ = false;
+}
+
+void Dhcpv4Srv::setPacketStatisticsDefaults() {
+ isc::stats::StatsMgr& stats_mgr = isc::stats::StatsMgr::instance();
+
+ // Iterate over set of observed statistics
+ for (auto it = dhcp4_statistics.begin(); it != dhcp4_statistics.end(); ++it) {
+ // Initialize them with default value 0
+ stats_mgr.setValue((*it), static_cast<int64_t>(0));
+ }
+}
+
+Dhcpv4Srv::~Dhcpv4Srv() {
+ // Discard any parked packets
+ discardPackets();
+
+ try {
+ stopD2();
+ } catch (const std::exception& ex) {
+ // Highly unlikely, but lets Report it but go on
+ LOG_ERROR(dhcp4_logger, DHCP4_SRV_D2STOP_ERROR).arg(ex.what());
+ }
+
+ try {
+ Dhcp4to6Ipc::instance().close();
+ } catch (const std::exception& ex) {
+ // Highly unlikely, but lets Report it but go on
+ LOG_ERROR(dhcp4_logger, DHCP4_SRV_DHCP4O6_ERROR).arg(ex.what());
+ }
+
+ IfaceMgr::instance().closeSockets();
+
+ // The lease manager was instantiated during DHCPv4Srv configuration,
+ // so we should clean up after ourselves.
+ LeaseMgrFactory::destroy();
+
+ // Explicitly unload hooks
+ HooksManager::prepareUnloadLibraries();
+ if (!HooksManager::unloadLibraries()) {
+ auto names = HooksManager::getLibraryNames();
+ std::string msg;
+ if (!names.empty()) {
+ msg = names[0];
+ for (size_t i = 1; i < names.size(); ++i) {
+ msg += std::string(", ") + names[i];
+ }
+ }
+ LOG_ERROR(dhcp4_logger, DHCP4_SRV_UNLOAD_LIBRARIES_ERROR).arg(msg);
+ }
+}
+
+void
+Dhcpv4Srv::shutdown() {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_SHUTDOWN_REQUEST);
+ shutdown_ = true;
+}
+
+isc::dhcp::Subnet4Ptr
+Dhcpv4Srv::selectSubnet(const Pkt4Ptr& query, bool& drop,
+ bool sanity_only) const {
+
+ // DHCPv4-over-DHCPv6 is a special (and complex) case
+ if (query->isDhcp4o6()) {
+ return (selectSubnet4o6(query, drop, sanity_only));
+ }
+
+ Subnet4Ptr subnet;
+
+ const SubnetSelector& selector = CfgSubnets4::initSelector(query);
+
+ CfgMgr& cfgmgr = CfgMgr::instance();
+ subnet = cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet(selector);
+
+ // Let's execute all callouts registered for subnet4_select
+ // (skip callouts if the selectSubnet was called to do sanity checks only)
+ if (!sanity_only &&
+ HooksManager::calloutsPresent(Hooks.hook_index_subnet4_select_)) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+ // Enable copying options from the packet within hook library.
+ ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
+
+ // Set new arguments
+ callout_handle->setArgument("query4", query);
+ callout_handle->setArgument("subnet4", subnet);
+ callout_handle->setArgument("subnet4collection",
+ cfgmgr.getCurrentCfg()->
+ getCfgSubnets4()->getAll());
+
+ // Call user (and server-side) callouts
+ HooksManager::callCallouts(Hooks.hook_index_subnet4_select_,
+ *callout_handle);
+
+ // Callouts decided to skip this step. This means that no subnet
+ // will be selected. Packet processing will continue, but it will
+ // be severely limited (i.e. only global options will be assigned)
+ if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
+ LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
+ DHCP4_HOOK_SUBNET4_SELECT_SKIP)
+ .arg(query->getLabel());
+ return (Subnet4Ptr());
+ }
+
+ // Callouts decided to drop the packet. It is a superset of the
+ // skip case so no subnet will be selected.
+ if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
+ LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
+ DHCP4_HOOK_SUBNET4_SELECT_DROP)
+ .arg(query->getLabel());
+ drop = true;
+ return (Subnet4Ptr());
+ }
+
+ // Use whatever subnet was specified by the callout
+ callout_handle->getArgument("subnet4", subnet);
+ }
+
+ if (subnet) {
+ // Log at higher debug level that subnet has been found.
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC_DATA, DHCP4_SUBNET_SELECTED)
+ .arg(query->getLabel())
+ .arg(subnet->getID());
+ // Log detailed information about the selected subnet at the
+ // lower debug level.
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_SUBNET_DATA)
+ .arg(query->getLabel())
+ .arg(subnet->toText());
+
+ } else {
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL,
+ DHCP4_SUBNET_SELECTION_FAILED)
+ .arg(query->getLabel());
+ }
+
+ return (subnet);
+}
+
+isc::dhcp::Subnet4Ptr
+Dhcpv4Srv::selectSubnet4o6(const Pkt4Ptr& query, bool& drop,
+ bool sanity_only) const {
+
+ Subnet4Ptr subnet;
+
+ SubnetSelector selector;
+ selector.ciaddr_ = query->getCiaddr();
+ selector.giaddr_ = query->getGiaddr();
+ selector.local_address_ = query->getLocalAddr();
+ selector.client_classes_ = query->classes_;
+ selector.iface_name_ = query->getIface();
+ // Mark it as DHCPv4-over-DHCPv6
+ selector.dhcp4o6_ = true;
+ // Now the DHCPv6 part
+ selector.remote_address_ = query->getRemoteAddr();
+ selector.first_relay_linkaddr_ = IOAddress("::");
+
+ // Handle a DHCPv6 relayed query
+ Pkt4o6Ptr query4o6 = boost::dynamic_pointer_cast<Pkt4o6>(query);
+ if (!query4o6) {
+ isc_throw(Unexpected, "Can't get DHCP4o6 message");
+ }
+ const Pkt6Ptr& query6 = query4o6->getPkt6();
+
+ // Initialize fields specific to relayed messages.
+ if (query6 && !query6->relay_info_.empty()) {
+ BOOST_REVERSE_FOREACH(Pkt6::RelayInfo relay, query6->relay_info_) {
+ if (!relay.linkaddr_.isV6Zero() &&
+ !relay.linkaddr_.isV6LinkLocal()) {
+ selector.first_relay_linkaddr_ = relay.linkaddr_;
+ break;
+ }
+ }
+ selector.interface_id_ =
+ query6->getAnyRelayOption(D6O_INTERFACE_ID,
+ Pkt6::RELAY_GET_FIRST);
+ }
+
+ // If the Subnet Selection option is present, extract its value.
+ OptionPtr sbnsel = query->getOption(DHO_SUBNET_SELECTION);
+ if (sbnsel) {
+ OptionCustomPtr oc = boost::dynamic_pointer_cast<OptionCustom>(sbnsel);
+ if (oc) {
+ selector.option_select_ = oc->readAddress();
+ }
+ }
+
+ CfgMgr& cfgmgr = CfgMgr::instance();
+ subnet = cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet4o6(selector);
+
+ // Let's execute all callouts registered for subnet4_select.
+ // (skip callouts if the selectSubnet was called to do sanity checks only)
+ if (!sanity_only &&
+ HooksManager::calloutsPresent(Hooks.hook_index_subnet4_select_)) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+ // Set new arguments
+ callout_handle->setArgument("query4", query);
+ callout_handle->setArgument("subnet4", subnet);
+ callout_handle->setArgument("subnet4collection",
+ cfgmgr.getCurrentCfg()->
+ getCfgSubnets4()->getAll());
+
+ // Call user (and server-side) callouts
+ HooksManager::callCallouts(Hooks.hook_index_subnet4_select_,
+ *callout_handle);
+
+ // Callouts decided to skip this step. This means that no subnet
+ // will be selected. Packet processing will continue, but it will
+ // be severely limited (i.e. only global options will be assigned)
+ if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
+ LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
+ DHCP4_HOOK_SUBNET4_SELECT_SKIP)
+ .arg(query->getLabel());
+ return (Subnet4Ptr());
+ }
+
+ // Callouts decided to drop the packet. It is a superset of the
+ // skip case so no subnet will be selected.
+ if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
+ LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
+ DHCP4_HOOK_SUBNET4_SELECT_DROP)
+ .arg(query->getLabel());
+ drop = true;
+ return (Subnet4Ptr());
+ }
+
+ // Use whatever subnet was specified by the callout
+ callout_handle->getArgument("subnet4", subnet);
+ }
+
+ if (subnet) {
+ // Log at higher debug level that subnet has been found.
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC_DATA, DHCP4_SUBNET_SELECTED)
+ .arg(query->getLabel())
+ .arg(subnet->getID());
+ // Log detailed information about the selected subnet at the
+ // lower debug level.
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_SUBNET_DATA)
+ .arg(query->getLabel())
+ .arg(subnet->toText());
+
+ } else {
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL,
+ DHCP4_SUBNET_SELECTION_FAILED)
+ .arg(query->getLabel());
+ }
+
+ return (subnet);
+}
+
+Pkt4Ptr
+Dhcpv4Srv::receivePacket(int timeout) {
+ return (IfaceMgr::instance().receive4(timeout));
+}
+
+void
+Dhcpv4Srv::sendPacket(const Pkt4Ptr& packet) {
+ IfaceMgr::instance().send(packet);
+}
+
+bool
+Dhcpv4Srv::earlyGHRLookup(const Pkt4Ptr& query,
+ AllocEngine::ClientContext4Ptr ctx) {
+ // Pointer to client's query.
+ ctx->query_ = query;
+
+ // Hardware address.
+ ctx->hwaddr_ = query->getHWAddr();
+
+ // Get the early-global-reservations-lookup flag value.
+ data::ConstElementPtr egrl = CfgMgr::instance().getCurrentCfg()->
+ getConfiguredGlobal(CfgGlobals::EARLY_GLOBAL_RESERVATIONS_LOOKUP);
+ if (egrl) {
+ ctx->early_global_reservations_lookup_ = egrl->boolValue();
+ }
+
+ // Perform early global reservations lookup when wanted.
+ if (ctx->early_global_reservations_lookup_) {
+ // Retrieve retrieve client identifier.
+ OptionPtr opt_clientid = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ if (opt_clientid) {
+ ctx->clientid_.reset(new ClientId(opt_clientid->getData()));
+ }
+
+ // Get the host identifiers.
+ Dhcpv4Exchange::setHostIdentifiers(ctx);
+
+ // Check for global host reservations.
+ ConstHostPtr global_host = alloc_engine_->findGlobalReservation(*ctx);
+
+ if (global_host && !global_host->getClientClasses4().empty()) {
+ // Remove dependent evaluated classes.
+ Dhcpv4Exchange::removeDependentEvaluatedClasses(query);
+
+ // Add classes from the global reservations.
+ const ClientClasses& classes = global_host->getClientClasses4();
+ for (ClientClasses::const_iterator cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ query->addClass(*cclass);
+ }
+
+ // Evaluate classes before KNOWN.
+ Dhcpv4Exchange::evaluateClasses(query, false);
+ }
+
+ if (global_host) {
+ // Add the KNOWN class;
+ query->addClass("KNOWN");
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_ASSIGNED)
+ .arg(query->getLabel())
+ .arg("KNOWN");
+
+ // Evaluate classes after KNOWN.
+ Dhcpv4Exchange::evaluateClasses(query, true);
+
+ // Check the DROP special class.
+ if (query->inClass("DROP")) {
+ LOG_DEBUG(packet4_logger, DBGLVL_PKT_HANDLING,
+ DHCP4_PACKET_DROP_0014)
+ .arg(query->toText());
+ isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
+ static_cast<int64_t>(1));
+ return (false);
+ }
+
+ // Store the reservation.
+ ctx->hosts_[SUBNET_ID_GLOBAL] = global_host;
+ }
+ }
+
+ return (true);
+}
+
+int
+Dhcpv4Srv::run() {
+#ifdef ENABLE_AFL
+ // Set up structures needed for fuzzing.
+ Fuzz fuzzer(4, server_port_);
+ //
+ // The next line is needed as a signature for AFL to recognize that we are
+ // running persistent fuzzing. This has to be in the main image file.
+ while (__AFL_LOOP(fuzzer.maxLoopCount())) {
+ // Read from stdin and put the data read into an address/port on which
+ // Kea is listening, read for Kea to read it via asynchronous I/O.
+ fuzzer.transfer();
+#else
+ while (!shutdown_) {
+#endif // ENABLE_AFL
+ try {
+ run_one();
+ getIOService()->poll();
+ } catch (const std::exception& e) {
+ // General catch-all exception that are not caught by more specific
+ // catches. This one is for exceptions derived from std::exception.
+ LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_STD_EXCEPTION)
+ .arg(e.what());
+ } catch (...) {
+ // General catch-all exception that are not caught by more specific
+ // catches. This one is for other exceptions, not derived from
+ // std::exception.
+ LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_EXCEPTION);
+ }
+ }
+
+ // Stop everything before we change into single-threaded mode.
+ MultiThreadingCriticalSection cs;
+
+ // destroying the thread pool
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+
+ return (getExitValue());
+}
+
+void
+Dhcpv4Srv::run_one() {
+ // client's message and server's response
+ Pkt4Ptr query;
+
+ try {
+ // Set select() timeout to 1s. This value should not be modified
+ // because it is important that the select() returns control
+ // frequently so as the IOService can be polled for ready handlers.
+ uint32_t timeout = 1;
+ query = receivePacket(timeout);
+
+ // Log if packet has arrived. We can't log the detailed information
+ // about the DHCP message because it hasn't been unpacked/parsed
+ // yet, and it can't be parsed at this point because hooks will
+ // have to process it first. The only information available at this
+ // point are: the interface, source address and destination addresses
+ // and ports.
+ if (query) {
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC, DHCP4_BUFFER_RECEIVED)
+ .arg(query->getRemoteAddr().toText())
+ .arg(query->getRemotePort())
+ .arg(query->getLocalAddr().toText())
+ .arg(query->getLocalPort())
+ .arg(query->getIface());
+ }
+
+ // We used to log that the wait was interrupted, but this is no longer
+ // the case. Our wait time is 1s now, so the lack of query packet more
+ // likely means that nothing new appeared within a second, rather than
+ // we were interrupted. And we don't want to print a message every
+ // second.
+
+ } catch (const SignalInterruptOnSelect&) {
+ // Packet reception interrupted because a signal has been received.
+ // This is not an error because we might have received a SIGTERM,
+ // SIGINT, SIGHUP or SIGCHLD which are handled by the server. For
+ // signals that are not handled by the server we rely on the default
+ // behavior of the system.
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL, DHCP4_BUFFER_WAIT_SIGNAL);
+ } catch (const std::exception& e) {
+ // Log all other errors.
+ LOG_ERROR(packet4_logger, DHCP4_BUFFER_RECEIVE_FAIL).arg(e.what());
+ }
+
+ // Timeout may be reached or signal received, which breaks select()
+ // with no reception occurred. No need to log anything here because
+ // we have logged right after the call to receivePacket().
+ if (!query) {
+ return;
+ }
+
+ // If the DHCP service has been globally disabled, drop the packet.
+ if (!network_state_->isServiceEnabled()) {
+ LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0008)
+ .arg(query->getLabel());
+ return;
+ } else {
+ if (MultiThreadingMgr::instance().getMode()) {
+ typedef function<void()> CallBack;
+ boost::shared_ptr<CallBack> call_back =
+ boost::make_shared<CallBack>(std::bind(&Dhcpv4Srv::processPacketAndSendResponseNoThrow,
+ this, query));
+ if (!MultiThreadingMgr::instance().getThreadPool().add(call_back)) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_PACKET_QUEUE_FULL);
+ }
+ } else {
+ processPacketAndSendResponse(query);
+ }
+ }
+}
+
+void
+Dhcpv4Srv::processPacketAndSendResponseNoThrow(Pkt4Ptr& query) {
+ try {
+ processPacketAndSendResponse(query);
+ } catch (const std::exception& e) {
+ LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_STD_EXCEPTION)
+ .arg(e.what());
+ } catch (...) {
+ LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_EXCEPTION);
+ }
+}
+
+void
+Dhcpv4Srv::processPacketAndSendResponse(Pkt4Ptr& query) {
+ Pkt4Ptr rsp;
+ processPacket(query, rsp);
+ if (!rsp) {
+ return;
+ }
+
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+ processPacketBufferSend(callout_handle, rsp);
+}
+
+void
+Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp, bool allow_packet_park) {
+ // Log reception of the packet. We need to increase it early, as any
+ // failures in unpacking will cause the packet to be dropped. We
+ // will increase type specific statistic further down the road.
+ // See processStatsReceived().
+ isc::stats::StatsMgr::instance().addValue("pkt4-received",
+ static_cast<int64_t>(1));
+
+ bool skip_unpack = false;
+
+ // The packet has just been received so contains the uninterpreted wire
+ // data; execute callouts registered for buffer4_receive.
+ if (HooksManager::calloutsPresent(Hooks.hook_index_buffer4_receive_)) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+ // Enable copying options from the packet within hook library.
+ ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
+
+ // Pass incoming packet as argument
+ callout_handle->setArgument("query4", query);
+
+ // Call callouts
+ HooksManager::callCallouts(Hooks.hook_index_buffer4_receive_,
+ *callout_handle);
+
+ // Callouts decided to drop the received packet.
+ // The response (rsp) is null so the caller (run_one) will
+ // immediately return too.
+ if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
+ LOG_DEBUG(hooks_logger, DBGLVL_PKT_HANDLING,
+ DHCP4_HOOK_BUFFER_RCVD_DROP)
+ .arg(query->getRemoteAddr().toText())
+ .arg(query->getLocalAddr().toText())
+ .arg(query->getIface());
+ return;
+ }
+
+ // Callouts decided to skip the next processing step. The next
+ // processing step would to parse the packet, so skip at this
+ // stage means that callouts did the parsing already, so server
+ // should skip parsing.
+ if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
+ LOG_DEBUG(hooks_logger, DBG_DHCP4_DETAIL,
+ DHCP4_HOOK_BUFFER_RCVD_SKIP)
+ .arg(query->getRemoteAddr().toText())
+ .arg(query->getLocalAddr().toText())
+ .arg(query->getIface());
+ skip_unpack = true;
+ }
+
+ callout_handle->getArgument("query4", query);
+ }
+
+ // Unpack the packet information unless the buffer4_receive callouts
+ // indicated they did it
+ if (!skip_unpack) {
+ try {
+ LOG_DEBUG(options4_logger, DBG_DHCP4_DETAIL, DHCP4_BUFFER_UNPACK)
+ .arg(query->getRemoteAddr().toText())
+ .arg(query->getLocalAddr().toText())
+ .arg(query->getIface());
+ query->unpack();
+ } catch (const SkipRemainingOptionsError& e) {
+ // An option failed to unpack but we are to attempt to process it
+ // anyway. Log it and let's hope for the best.
+ LOG_DEBUG(options4_logger, DBG_DHCP4_DETAIL,
+ DHCP4_PACKET_OPTIONS_SKIPPED)
+ .arg(e.what());
+ } catch (const std::exception& e) {
+ // Failed to parse the packet.
+ LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0001)
+ .arg(query->getRemoteAddr().toText())
+ .arg(query->getLocalAddr().toText())
+ .arg(query->getIface())
+ .arg(e.what());
+
+ // Increase the statistics of parse failures and dropped packets.
+ isc::stats::StatsMgr::instance().addValue("pkt4-parse-failed",
+ static_cast<int64_t>(1));
+ isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
+ static_cast<int64_t>(1));
+ return;
+ }
+ }
+
+ // Update statistics accordingly for received packet.
+ processStatsReceived(query);
+
+ // Assign this packet to one or more classes if needed. We need to do
+ // this before calling accept(), because getSubnet4() may need client
+ // class information.
+ classifyPacket(query);
+
+ // Now it is classified the deferred unpacking can be done.
+ deferredUnpack(query);
+
+ // Check whether the message should be further processed or discarded.
+ // There is no need to log anything here. This function logs by itself.
+ if (!accept(query)) {
+ // Increase the statistic of dropped packets.
+ isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
+ static_cast<int64_t>(1));
+ return;
+ }
+
+ // We have sanity checked (in accept() that the Message Type option
+ // exists, so we can safely get it here.
+ int type = query->getType();
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC_DATA, DHCP4_PACKET_RECEIVED)
+ .arg(query->getLabel())
+ .arg(query->getName())
+ .arg(type)
+ .arg(query->getRemoteAddr())
+ .arg(query->getLocalAddr())
+ .arg(query->getIface());
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_QUERY_DATA)
+ .arg(query->getLabel())
+ .arg(query->toText());
+
+ // Let's execute all callouts registered for pkt4_receive
+ if (HooksManager::calloutsPresent(Hooks.hook_index_pkt4_receive_)) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+ // Enable copying options from the packet within hook library.
+ ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
+
+ // Pass incoming packet as argument
+ callout_handle->setArgument("query4", query);
+
+ // Call callouts
+ HooksManager::callCallouts(Hooks.hook_index_pkt4_receive_,
+ *callout_handle);
+
+ // Callouts decided to skip the next processing step. The next
+ // processing step would to process the packet, so skip at this
+ // stage means drop.
+ if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
+ (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
+ LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
+ DHCP4_HOOK_PACKET_RCVD_SKIP)
+ .arg(query->getLabel());
+ return;
+ }
+
+ callout_handle->getArgument("query4", query);
+ }
+
+ // Check the DROP special class.
+ if (query->inClass("DROP")) {
+ LOG_DEBUG(packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0010)
+ .arg(query->toText());
+ isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
+ static_cast<int64_t>(1));
+ return;
+ }
+
+ processDhcp4Query(query, rsp, allow_packet_park);
+}
+
+void
+Dhcpv4Srv::processDhcp4QueryAndSendResponse(Pkt4Ptr& query, Pkt4Ptr& rsp,
+ bool allow_packet_park) {
+ try {
+ processDhcp4Query(query, rsp, allow_packet_park);
+ if (!rsp) {
+ return;
+ }
+
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+ processPacketBufferSend(callout_handle, rsp);
+ } catch (const std::exception& e) {
+ LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_STD_EXCEPTION)
+ .arg(e.what());
+ } catch (...) {
+ LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_EXCEPTION);
+ }
+}
+
+void
+Dhcpv4Srv::processDhcp4Query(Pkt4Ptr& query, Pkt4Ptr& rsp,
+ bool allow_packet_park) {
+ // Create a client race avoidance RAII handler.
+ ClientHandler client_handler;
+
+ // Check for lease modifier queries from the same client being processed.
+ if (MultiThreadingMgr::instance().getMode() &&
+ ((query->getType() == DHCPDISCOVER) ||
+ (query->getType() == DHCPREQUEST) ||
+ (query->getType() == DHCPRELEASE) ||
+ (query->getType() == DHCPDECLINE))) {
+ ContinuationPtr cont =
+ makeContinuation(std::bind(&Dhcpv4Srv::processDhcp4QueryAndSendResponse,
+ this, query, rsp, allow_packet_park));
+ if (!client_handler.tryLock(query, cont)) {
+ return;
+ }
+ }
+
+ AllocEngine::ClientContext4Ptr ctx(new AllocEngine::ClientContext4());
+ if (!earlyGHRLookup(query, ctx)) {
+ return;
+ }
+
+ try {
+ switch (query->getType()) {
+ case DHCPDISCOVER:
+ rsp = processDiscover(query, ctx);
+ break;
+
+ case DHCPREQUEST:
+ // Note that REQUEST is used for many things in DHCPv4: for
+ // requesting new leases, renewing existing ones and even
+ // for rebinding.
+ rsp = processRequest(query, ctx);
+ break;
+
+ case DHCPRELEASE:
+ processRelease(query, ctx);
+ break;
+
+ case DHCPDECLINE:
+ processDecline(query, ctx);
+ break;
+
+ case DHCPINFORM:
+ rsp = processInform(query, ctx);
+ break;
+
+ default:
+ // Only action is to output a message if debug is enabled,
+ // and that is covered by the debug statement before the
+ // "switch" statement.
+ ;
+ }
+ } catch (const std::exception& e) {
+
+ // Catch-all exception (we used to call only isc::Exception, but
+ // std::exception could potentially be raised and if we don't catch
+ // it here, it would be caught in main() and the process would
+ // terminate). Just log the problem and ignore the packet.
+ // (The problem is logged as a debug message because debug is
+ // disabled by default - it prevents a DDOS attack based on the
+ // sending of problem packets.)
+ LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0007)
+ .arg(query->getLabel())
+ .arg(e.what());
+
+ // Increase the statistic of dropped packets.
+ isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
+ static_cast<int64_t>(1));
+ }
+
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+ if (ctx && HooksManager::calloutsPresent(Hooks.hook_index_leases4_committed_)) {
+ // The ScopedCalloutHandleState class which guarantees that the task
+ // is added to the thread pool after the response is reset (if needed)
+ // and CalloutHandle state is reset. In ST it does nothing.
+ // A smart pointer is used to store the ScopedCalloutHandleState so that
+ // a copy of the pointer is created by the lambda and only on the
+ // destruction of the last reference the task is added.
+ // In MT there are 2 cases:
+ // 1. packet is unparked before current thread smart pointer to
+ // ScopedCalloutHandleState is destroyed:
+ // - the lambda uses the smart pointer to set the callout which adds the
+ // task, but the task is added after ScopedCalloutHandleState is
+ // destroyed, on the destruction of the last reference which is held
+ // by the current thread.
+ // 2. packet is unparked after the current thread smart pointer to
+ // ScopedCalloutHandleState is destroyed:
+ // - the current thread reference to ScopedCalloutHandleState is
+ // destroyed, but the reference in the lambda keeps it alive until
+ // the lambda is called and the last reference is released, at which
+ // time the task is actually added.
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ std::shared_ptr<ScopedCalloutHandleState> callout_handle_state =
+ std::make_shared<ScopedCalloutHandleState>(callout_handle);
+
+ ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
+
+ // Also pass the corresponding query packet as argument
+ callout_handle->setArgument("query4", query);
+
+ Lease4CollectionPtr new_leases(new Lease4Collection());
+ // Filter out the new lease if it was reused so not committed.
+ if (ctx->new_lease_ && (ctx->new_lease_->reuseable_valid_lft_ == 0)) {
+ new_leases->push_back(ctx->new_lease_);
+ }
+ callout_handle->setArgument("leases4", new_leases);
+
+ Lease4CollectionPtr deleted_leases(new Lease4Collection());
+ if (ctx->old_lease_) {
+ if ((!ctx->new_lease_) || (ctx->new_lease_->addr_ != ctx->old_lease_->addr_)) {
+ deleted_leases->push_back(ctx->old_lease_);
+ }
+ }
+ callout_handle->setArgument("deleted_leases4", deleted_leases);
+
+ if (allow_packet_park) {
+ // Get the parking limit. Parsing should ensure the value is present.
+ uint32_t parked_packet_limit = 0;
+ data::ConstElementPtr ppl = CfgMgr::instance().getCurrentCfg()->
+ getConfiguredGlobal(CfgGlobals::PARKED_PACKET_LIMIT);
+ if (ppl) {
+ parked_packet_limit = ppl->intValue();
+ }
+
+ if (parked_packet_limit) {
+ const auto& parking_lot = ServerHooks::getServerHooks().
+ getParkingLotPtr("leases4_committed");
+
+ if (parking_lot && (parking_lot->size() >= parked_packet_limit)) {
+ // We can't park it so we're going to throw it on the floor.
+ LOG_DEBUG(packet4_logger, DBGLVL_PKT_HANDLING,
+ DHCP4_HOOK_LEASES4_PARKING_LOT_FULL)
+ .arg(parked_packet_limit)
+ .arg(query->getLabel());
+ isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
+ static_cast<int64_t>(1));
+ rsp.reset();
+ return;
+ }
+ }
+
+ // We proactively park the packet. We'll unpark it without invoking
+ // the callback (i.e. drop) unless the callout status is set to
+ // NEXT_STEP_PARK. Otherwise the callback we bind here will be
+ // executed when the hook library unparks the packet.
+ HooksManager::park("leases4_committed", query,
+ [this, callout_handle, query, rsp, callout_handle_state]() mutable {
+ if (MultiThreadingMgr::instance().getMode()) {
+ typedef function<void()> CallBack;
+ boost::shared_ptr<CallBack> call_back =
+ boost::make_shared<CallBack>(std::bind(&Dhcpv4Srv::sendResponseNoThrow,
+ this, callout_handle, query, rsp));
+ callout_handle_state->on_completion_ = [call_back]() {
+ MultiThreadingMgr::instance().getThreadPool().add(call_back);
+ };
+ } else {
+ processPacketPktSend(callout_handle, query, rsp);
+ processPacketBufferSend(callout_handle, rsp);
+ }
+ });
+ }
+
+ try {
+ // Call all installed callouts
+ HooksManager::callCallouts(Hooks.hook_index_leases4_committed_,
+ *callout_handle);
+ } catch (...) {
+ // Make sure we don't orphan a parked packet.
+ if (allow_packet_park) {
+ HooksManager::drop("leases4_committed", query);
+ }
+
+ throw;
+ }
+
+ if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_PARK)
+ && allow_packet_park) {
+ LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS, DHCP4_HOOK_LEASES4_COMMITTED_PARK)
+ .arg(query->getLabel());
+ // Since the hook library(ies) are going to do the unparking, then
+ // reset the pointer to the response to indicate to the caller that
+ // it should return, as the packet processing will continue via
+ // the callback.
+ rsp.reset();
+ } else {
+ // Drop the park job on the packet, it isn't needed.
+ HooksManager::drop("leases4_committed", query);
+ if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
+ LOG_DEBUG(hooks_logger, DBGLVL_PKT_HANDLING, DHCP4_HOOK_LEASES4_COMMITTED_DROP)
+ .arg(query->getLabel());
+ rsp.reset();
+ }
+ }
+ }
+
+ // If we have a response prep it for shipment.
+ if (rsp) {
+ processPacketPktSend(callout_handle, query, rsp);
+ }
+}
+
+void
+Dhcpv4Srv::sendResponseNoThrow(hooks::CalloutHandlePtr& callout_handle,
+ Pkt4Ptr& query, Pkt4Ptr& rsp) {
+ try {
+ processPacketPktSend(callout_handle, query, rsp);
+ processPacketBufferSend(callout_handle, rsp);
+ } catch (const std::exception& e) {
+ LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_STD_EXCEPTION)
+ .arg(e.what());
+ } catch (...) {
+ LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_EXCEPTION);
+ }
+}
+
+void
+Dhcpv4Srv::processPacketPktSend(hooks::CalloutHandlePtr& callout_handle,
+ Pkt4Ptr& query, Pkt4Ptr& rsp) {
+ if (!rsp) {
+ return;
+ }
+
+ // Specifies if server should do the packing
+ bool skip_pack = false;
+
+ // Execute all callouts registered for pkt4_send
+ if (HooksManager::calloutsPresent(Hooks.hook_index_pkt4_send_)) {
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+ // Enable copying options from the query and response packets within
+ // hook library.
+ ScopedEnableOptionsCopy<Pkt4> query_resp_options_copy(query, rsp);
+
+ // Pass incoming packet as argument
+ callout_handle->setArgument("query4", query);
+
+ // Set our response
+ callout_handle->setArgument("response4", rsp);
+
+ // Call all installed callouts
+ HooksManager::callCallouts(Hooks.hook_index_pkt4_send_,
+ *callout_handle);
+
+ // Callouts decided to skip the next processing step. The next
+ // processing step would to pack the packet (create wire data).
+ // That step will be skipped if any callout sets skip flag.
+ // It essentially means that the callout already did packing,
+ // so the server does not have to do it again.
+ if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
+ LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS, DHCP4_HOOK_PACKET_SEND_SKIP)
+ .arg(query->getLabel());
+ skip_pack = true;
+ }
+
+ /// Callouts decided to drop the packet.
+ if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
+ LOG_DEBUG(hooks_logger, DBGLVL_PKT_HANDLING, DHCP4_HOOK_PACKET_SEND_DROP)
+ .arg(rsp->getLabel());
+ rsp.reset();
+ return;
+ }
+ }
+
+ if (!skip_pack) {
+ try {
+ LOG_DEBUG(options4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_PACK)
+ .arg(rsp->getLabel());
+ rsp->pack();
+ } catch (const std::exception& e) {
+ LOG_ERROR(options4_logger, DHCP4_PACKET_PACK_FAIL)
+ .arg(rsp->getLabel())
+ .arg(e.what());
+ }
+ }
+}
+
+void
+Dhcpv4Srv::processPacketBufferSend(CalloutHandlePtr& callout_handle,
+ Pkt4Ptr& rsp) {
+ if (!rsp) {
+ return;
+ }
+
+ try {
+ // Now all fields and options are constructed into output wire buffer.
+ // Option objects modification does not make sense anymore. Hooks
+ // can only manipulate wire buffer at this stage.
+ // Let's execute all callouts registered for buffer4_send
+ if (HooksManager::calloutsPresent(Hooks.hook_index_buffer4_send_)) {
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+ // Enable copying options from the packet within hook library.
+ ScopedEnableOptionsCopy<Pkt4> resp4_options_copy(rsp);
+
+ // Pass incoming packet as argument
+ callout_handle->setArgument("response4", rsp);
+
+ // Call callouts
+ HooksManager::callCallouts(Hooks.hook_index_buffer4_send_,
+ *callout_handle);
+
+ // Callouts decided to skip the next processing step. The next
+ // processing step would to parse the packet, so skip at this
+ // stage means drop.
+ if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
+ (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
+ LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
+ DHCP4_HOOK_BUFFER_SEND_SKIP)
+ .arg(rsp->getLabel());
+ return;
+ }
+
+ callout_handle->getArgument("response4", rsp);
+ }
+
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC, DHCP4_PACKET_SEND)
+ .arg(rsp->getLabel())
+ .arg(rsp->getName())
+ .arg(static_cast<int>(rsp->getType()))
+ .arg(rsp->getLocalAddr().isV4Zero() ? "*" : rsp->getLocalAddr().toText())
+ .arg(rsp->getLocalPort())
+ .arg(rsp->getRemoteAddr())
+ .arg(rsp->getRemotePort())
+ .arg(rsp->getIface().empty() ? "to be determined from routing" :
+ rsp->getIface());
+
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA,
+ DHCP4_RESPONSE_DATA)
+ .arg(rsp->getLabel())
+ .arg(rsp->getName())
+ .arg(static_cast<int>(rsp->getType()))
+ .arg(rsp->toText());
+ sendPacket(rsp);
+
+ // Update statistics accordingly for sent packet.
+ processStatsSent(rsp);
+
+ } catch (const std::exception& e) {
+ LOG_ERROR(packet4_logger, DHCP4_PACKET_SEND_FAIL)
+ .arg(rsp->getLabel())
+ .arg(e.what());
+ }
+}
+
+string
+Dhcpv4Srv::srvidToString(const OptionPtr& srvid) {
+ if (!srvid) {
+ isc_throw(BadValue, "NULL pointer passed to srvidToString()");
+ }
+ boost::shared_ptr<Option4AddrLst> generated =
+ boost::dynamic_pointer_cast<Option4AddrLst>(srvid);
+ if (!srvid) {
+ isc_throw(BadValue, "Pointer to invalid option passed to srvidToString()");
+ }
+
+ Option4AddrLst::AddressContainer addrs = generated->getAddresses();
+ if (addrs.size() != 1) {
+ isc_throw(BadValue, "Malformed option passed to srvidToString(). "
+ << "Expected to contain a single IPv4 address.");
+ }
+
+ return (addrs[0].toText());
+}
+
+void
+Dhcpv4Srv::appendServerID(Dhcpv4Exchange& ex) {
+
+ // Do not append generated server identifier if there is one appended already.
+ // This is when explicitly configured server identifier option is present.
+ if (ex.getResponse()->getOption(DHO_DHCP_SERVER_IDENTIFIER)) {
+ return;
+ }
+
+ // Use local address on which the packet has been received as a
+ // server identifier. In some cases it may be a different address,
+ // e.g. broadcast packet or DHCPv4o6 packet.
+ IOAddress local_addr = ex.getQuery()->getLocalAddr();
+ Pkt4Ptr query = ex.getQuery();
+
+ if (local_addr.isV4Bcast() || query->isDhcp4o6()) {
+ local_addr = IfaceMgr::instance().getSocket(query).addr_;
+ }
+
+ OptionPtr opt_srvid(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
+ local_addr));
+ ex.getResponse()->addOption(opt_srvid);
+}
+
+void
+Dhcpv4Srv::buildCfgOptionList(Dhcpv4Exchange& ex) {
+ CfgOptionList& co_list = ex.getCfgOptionList();
+
+ // Retrieve subnet.
+ Subnet4Ptr subnet = ex.getContext()->subnet_;
+ if (!subnet) {
+ // All methods using the CfgOptionList object return soon when
+ // there is no subnet so do the same
+ return;
+ }
+
+ // Firstly, host specific options.
+ const ConstHostPtr& host = ex.getContext()->currentHost();
+ if (host && !host->getCfgOption4()->empty()) {
+ co_list.push_back(host->getCfgOption4());
+ }
+
+ // Secondly, pool specific options.
+ Pkt4Ptr resp = ex.getResponse();
+ IOAddress addr = IOAddress::IPV4_ZERO_ADDRESS();
+ if (resp) {
+ addr = resp->getYiaddr();
+ }
+ if (!addr.isV4Zero()) {
+ PoolPtr pool = subnet->getPool(Lease::TYPE_V4, addr, false);
+ if (pool && !pool->getCfgOption()->empty()) {
+ co_list.push_back(pool->getCfgOption());
+ }
+ }
+
+ // Thirdly, subnet configured options.
+ if (!subnet->getCfgOption()->empty()) {
+ co_list.push_back(subnet->getCfgOption());
+ }
+
+ // Fourthly, shared network specific options.
+ SharedNetwork4Ptr network;
+ subnet->getSharedNetwork(network);
+ if (network && !network->getCfgOption()->empty()) {
+ co_list.push_back(network->getCfgOption());
+ }
+
+ // Each class in the incoming packet
+ const ClientClasses& classes = ex.getQuery()->getClasses();
+ for (ClientClasses::const_iterator cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ // Find the client class definition for this class
+ const ClientClassDefPtr& ccdef = CfgMgr::instance().getCurrentCfg()->
+ getClientClassDictionary()->findClass(*cclass);
+ if (!ccdef) {
+ // Not found: the class is built-in or not configured
+ if (!isClientClassBuiltIn(*cclass)) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_UNCONFIGURED)
+ .arg(ex.getQuery()->getLabel())
+ .arg(*cclass);
+ }
+ // Skip it
+ continue;
+ }
+
+ if (ccdef->getCfgOption()->empty()) {
+ // Skip classes which don't configure options
+ continue;
+ }
+
+ co_list.push_back(ccdef->getCfgOption());
+ }
+
+ // Last global options
+ if (!CfgMgr::instance().getCurrentCfg()->getCfgOption()->empty()) {
+ co_list.push_back(CfgMgr::instance().getCurrentCfg()->getCfgOption());
+ }
+}
+
+void
+Dhcpv4Srv::appendRequestedOptions(Dhcpv4Exchange& ex) {
+ // Get the subnet relevant for the client. We will need it
+ // to get the options associated with it.
+ Subnet4Ptr subnet = ex.getContext()->subnet_;
+ // If we can't find the subnet for the client there is no way
+ // to get the options to be sent to a client. We don't log an
+ // error because it will be logged by the assignLease method
+ // anyway.
+ if (!subnet) {
+ return;
+ }
+
+ // Unlikely short cut
+ const CfgOptionList& co_list = ex.getCfgOptionList();
+ if (co_list.empty()) {
+ return;
+ }
+
+ Pkt4Ptr query = ex.getQuery();
+ Pkt4Ptr resp = ex.getResponse();
+ std::vector<uint8_t> requested_opts;
+
+ // try to get the 'Parameter Request List' option which holds the
+ // codes of requested options.
+ OptionUint8ArrayPtr option_prl = boost::dynamic_pointer_cast<
+ OptionUint8Array>(query->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST));
+ // Get the codes of requested options.
+ if (option_prl) {
+ requested_opts = option_prl->getValues();
+ }
+ // Iterate on the configured option list to add persistent options
+ for (CfgOptionList::const_iterator copts = co_list.begin();
+ copts != co_list.end(); ++copts) {
+ const OptionContainerPtr& opts = (*copts)->getAll(DHCP4_OPTION_SPACE);
+ if (!opts) {
+ continue;
+ }
+ // Get persistent options
+ const OptionContainerPersistIndex& idx = opts->get<2>();
+ const OptionContainerPersistRange& range = idx.equal_range(true);
+ for (OptionContainerPersistIndex::const_iterator desc = range.first;
+ desc != range.second; ++desc) {
+ // Add the persistent option code to requested options
+ if (desc->option_) {
+ uint8_t code = static_cast<uint8_t>(desc->option_->getType());
+ requested_opts.push_back(code);
+ }
+ }
+ }
+
+ // For each requested option code get the instance of the option
+ // to be returned to the client.
+ for (std::vector<uint8_t>::const_iterator opt = requested_opts.begin();
+ opt != requested_opts.end(); ++opt) {
+ // Add nothing when it is already there
+ if (!resp->getOption(*opt)) {
+ // Iterate on the configured option list
+ for (CfgOptionList::const_iterator copts = co_list.begin();
+ copts != co_list.end(); ++copts) {
+ OptionDescriptor desc = (*copts)->get(DHCP4_OPTION_SPACE, *opt);
+ // Got it: add it and jump to the outer loop
+ if (desc.option_) {
+ resp->addOption(desc.option_);
+ break;
+ }
+ }
+ }
+ }
+}
+
+void
+Dhcpv4Srv::appendRequestedVendorOptions(Dhcpv4Exchange& ex) {
+ // Get the configured subnet suitable for the incoming packet.
+ Subnet4Ptr subnet = ex.getContext()->subnet_;
+ // Leave if there is no subnet matching the incoming packet.
+ // There is no need to log the error message here because
+ // it will be logged in the assignLease() when it fails to
+ // pick the suitable subnet. We don't want to duplicate
+ // error messages in such case.
+ if (!subnet) {
+ return;
+ }
+
+ // Unlikely short cut
+ const CfgOptionList& co_list = ex.getCfgOptionList();
+ if (co_list.empty()) {
+ return;
+ }
+
+ uint32_t vendor_id = 0;
+
+ // Try to get the vendor option from the client packet. This is how it's
+ // supposed to be done. Client sends vivso, we look at the vendor-id and
+ // then send back the vendor options specific to that client.
+ boost::shared_ptr<OptionVendor> vendor_req = boost::dynamic_pointer_cast<
+ OptionVendor>(ex.getQuery()->getOption(DHO_VIVSO_SUBOPTIONS));
+ if (vendor_req) {
+ vendor_id = vendor_req->getVendorId();
+ }
+
+ // Something is fishy. Client was supposed to send vivso, but didn't.
+ // Let's try an alternative. It's possible that the server already
+ // inserted vivso in the response message, (e.g. by using client
+ // classification or perhaps a hook inserted it).
+ boost::shared_ptr<OptionVendor> vendor_rsp = boost::dynamic_pointer_cast<
+ OptionVendor>(ex.getResponse()->getOption(DHO_VIVSO_SUBOPTIONS));
+ if (vendor_rsp) {
+ vendor_id = vendor_rsp->getVendorId();
+ }
+
+ if (!vendor_req && !vendor_rsp) {
+ // Ok, we're out of luck today. Neither client nor server packets
+ // have vivso. There is no way to figure out vendor-id here.
+ // We give up.
+ return;
+ }
+
+ std::vector<uint8_t> requested_opts;
+
+ // Let's try to get ORO within that vendor-option.
+ // This is specific to vendor-id=4491 (Cable Labs). Other vendors may have
+ // different policies.
+ OptionUint8ArrayPtr oro;
+ if (vendor_id == VENDOR_ID_CABLE_LABS && vendor_req) {
+ OptionPtr oro_generic = vendor_req->getOption(DOCSIS3_V4_ORO);
+ if (oro_generic) {
+ // Vendor ID 4491 makes Kea look at DOCSIS3_V4_OPTION_DEFINITIONS
+ // when parsing options. Based on that, oro_generic will have been
+ // created as an OptionUint8Array, but might not be for other
+ // vendor IDs.
+ oro = boost::dynamic_pointer_cast<OptionUint8Array>(oro_generic);
+ // Get the list of options that client requested.
+ if (oro) {
+ requested_opts = oro->getValues();
+ }
+ }
+ }
+
+ // Iterate on the configured option list to add persistent options
+ for (CfgOptionList::const_iterator copts = co_list.begin();
+ copts != co_list.end(); ++copts) {
+ const OptionContainerPtr& opts = (*copts)->getAll(vendor_id);
+ if (!opts) {
+ continue;
+ }
+
+ // Get persistent options
+ const OptionContainerPersistIndex& idx = opts->get<2>();
+ const OptionContainerPersistRange& range = idx.equal_range(true);
+ for (OptionContainerPersistIndex::const_iterator desc = range.first;
+ desc != range.second; ++desc) {
+ // Add the persistent option code to requested options
+ if (desc->option_) {
+ uint8_t code = static_cast<uint8_t>(desc->option_->getType());
+ requested_opts.push_back(code);
+ }
+ }
+ }
+
+ // If there is nothing to add don't do anything then.
+ if (requested_opts.empty()) {
+ return;
+ }
+
+ if (!vendor_rsp) {
+ // It's possible that vivso was inserted already by client class or
+ // a hook. If that is so, let's use it.
+ vendor_rsp.reset(new OptionVendor(Option::V4, vendor_id));
+ }
+
+ // Get the list of options that client requested.
+ bool added = false;
+ for (std::vector<uint8_t>::const_iterator code = requested_opts.begin();
+ code != requested_opts.end(); ++code) {
+ if (!vendor_rsp->getOption(*code)) {
+ for (CfgOptionList::const_iterator copts = co_list.begin();
+ copts != co_list.end(); ++copts) {
+ OptionDescriptor desc = (*copts)->get(vendor_id, *code);
+ if (desc.option_) {
+ vendor_rsp->addOption(desc.option_);
+ added = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // If we added some sub-options and the vivso option is not in
+ // the response already, then add it.
+ if (added && !ex.getResponse()->getOption(DHO_VIVSO_SUBOPTIONS)) {
+ ex.getResponse()->addOption(vendor_rsp);
+ }
+}
+
+void
+Dhcpv4Srv::appendBasicOptions(Dhcpv4Exchange& ex) {
+ // Identify options that we always want to send to the
+ // client (if they are configured).
+ static const uint16_t required_options[] = {
+ DHO_ROUTERS,
+ DHO_DOMAIN_NAME_SERVERS,
+ DHO_DOMAIN_NAME,
+ DHO_DHCP_SERVER_IDENTIFIER };
+
+ static size_t required_options_size =
+ sizeof(required_options) / sizeof(required_options[0]);
+
+ // Get the subnet.
+ Subnet4Ptr subnet = ex.getContext()->subnet_;
+ if (!subnet) {
+ return;
+ }
+
+ // Unlikely short cut
+ const CfgOptionList& co_list = ex.getCfgOptionList();
+ if (co_list.empty()) {
+ return;
+ }
+
+ Pkt4Ptr resp = ex.getResponse();
+
+ // Try to find all 'required' options in the outgoing
+ // message. Those that are not present will be added.
+ for (int i = 0; i < required_options_size; ++i) {
+ OptionPtr opt = resp->getOption(required_options[i]);
+ if (!opt) {
+ // Check whether option has been configured.
+ for (CfgOptionList::const_iterator copts = co_list.begin();
+ copts != co_list.end(); ++copts) {
+ OptionDescriptor desc = (*copts)->get(DHCP4_OPTION_SPACE,
+ required_options[i]);
+ if (desc.option_) {
+ resp->addOption(desc.option_);
+ break;
+ }
+ }
+ }
+ }
+}
+
+void
+Dhcpv4Srv::processClientName(Dhcpv4Exchange& ex) {
+ // It is possible that client has sent both Client FQDN and Hostname
+ // option. In that the server should prefer Client FQDN option and
+ // ignore the Hostname option.
+ try {
+ Pkt4Ptr query = ex.getQuery();
+ Pkt4Ptr resp = ex.getResponse();
+ Option4ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>
+ (query->getOption(DHO_FQDN));
+ if (fqdn) {
+ LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL, DHCP4_CLIENT_FQDN_PROCESS)
+ .arg(query->getLabel());
+ processClientFqdnOption(ex);
+
+ } else {
+ LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL,
+ DHCP4_CLIENT_HOSTNAME_PROCESS)
+ .arg(query->getLabel());
+ processHostnameOption(ex);
+ }
+
+ // Based on the output option added to the response above, we figure out
+ // the values for the hostname and dns flags to set in the context. These
+ // will be used to populate the lease.
+ std::string hostname;
+ bool fqdn_fwd = false;
+ bool fqdn_rev = false;
+
+
+ OptionStringPtr opt_hostname;
+ fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
+ if (fqdn) {
+ hostname = fqdn->getDomainName();
+ CfgMgr::instance().getD2ClientMgr().getUpdateDirections(*fqdn, fqdn_fwd, fqdn_rev);
+ } else {
+ opt_hostname = boost::dynamic_pointer_cast<OptionString>
+ (resp->getOption(DHO_HOST_NAME));
+
+ if (opt_hostname) {
+ hostname = opt_hostname->getValue();
+ // DHO_HOST_NAME is string option which cannot be blank,
+ // we use "." to know we should replace it with a fully
+ // generated name. The local string variable needs to be
+ // blank in logic below.
+ if (hostname == ".") {
+ hostname = "";
+ }
+
+ /// @todo It could be configurable what sort of updates the
+ /// server is doing when Hostname option was sent.
+ if (ex.getContext()->getDdnsParams()->getEnableUpdates()) {
+ fqdn_fwd = true;
+ fqdn_rev = true;
+ }
+ }
+ }
+
+ // Optionally, call a hook that may possibly override the decisions made
+ // earlier.
+ if (HooksManager::calloutsPresent(Hooks.hook_index_ddns4_update_)) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+ // Setup the callout arguments.
+ Subnet4Ptr subnet = ex.getContext()->subnet_;
+ callout_handle->setArgument("query4", query);
+ callout_handle->setArgument("response4", resp);
+ callout_handle->setArgument("subnet4", subnet);
+ callout_handle->setArgument("hostname", hostname);
+ callout_handle->setArgument("fwd-update", fqdn_fwd);
+ callout_handle->setArgument("rev-update", fqdn_rev);
+ callout_handle->setArgument("ddns-params", ex.getContext()->getDdnsParams());
+
+ // Call callouts
+ HooksManager::callCallouts(Hooks.hook_index_ddns4_update_, *callout_handle);
+
+ // Let's get the parameters returned by hook.
+ string hook_hostname;
+ bool hook_fqdn_fwd = false;
+ bool hook_fqdn_rev = false;
+ callout_handle->getArgument("hostname", hook_hostname);
+ callout_handle->getArgument("fwd-update", hook_fqdn_fwd);
+ callout_handle->getArgument("rev-update", hook_fqdn_rev);
+
+ // If there's anything changed by the hook, log it and then update
+ // the parameters.
+ if ((hostname != hook_hostname) || (fqdn_fwd != hook_fqdn_fwd) ||
+ (fqdn_rev != hook_fqdn_rev)) {
+ LOG_DEBUG(hooks_logger, DBGLVL_PKT_HANDLING, DHCP4_HOOK_DDNS_UPDATE)
+ .arg(hostname).arg(hook_hostname).arg(fqdn_fwd).arg(hook_fqdn_fwd)
+ .arg(fqdn_rev).arg(hook_fqdn_rev);
+ hostname = hook_hostname;
+ fqdn_fwd = hook_fqdn_fwd;
+ fqdn_rev = hook_fqdn_rev;
+
+ // If there's an outbound host-name option in the response we
+ // need to updated it with the new host name.
+ OptionStringPtr hostname_opt = boost::dynamic_pointer_cast<OptionString>
+ (resp->getOption(DHO_HOST_NAME));
+ if (hostname_opt) {
+ hostname_opt->setValue(hook_hostname);
+ }
+
+ // If there's an outbound FQDN option in the response we need
+ // to update it with the new host name.
+ Option4ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>
+ (resp->getOption(DHO_FQDN));
+ if (fqdn) {
+ fqdn->setDomainName(hook_hostname, Option4ClientFqdn::FULL);
+ // Hook disabled updates, Set flags back to client accordingly.
+ fqdn->setFlag(Option4ClientFqdn::FLAG_S, 0);
+ fqdn->setFlag(Option4ClientFqdn::FLAG_N, 1);
+ }
+ }
+ }
+
+ // Update the context
+ auto ctx = ex.getContext();
+ ctx->fwd_dns_update_ = fqdn_fwd;
+ ctx->rev_dns_update_ = fqdn_rev;
+ ctx->hostname_ = hostname;
+
+ } catch (const Exception& e) {
+ // In some rare cases it is possible that the client's name processing
+ // fails. For example, the Hostname option may be malformed, or there
+ // may be an error in the server's logic which would cause multiple
+ // attempts to add the same option to the response message. This
+ // error message aggregates all these errors so they can be diagnosed
+ // from the log. We don't want to throw an exception here because,
+ // it will impact the processing of the whole packet. We rather want
+ // the processing to continue, even if the client's name is wrong.
+ LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_CLIENT_NAME_PROC_FAIL)
+ .arg(ex.getQuery()->getLabel())
+ .arg(e.what());
+ }
+}
+
+void
+Dhcpv4Srv::processClientFqdnOption(Dhcpv4Exchange& ex) {
+ // Obtain the FQDN option from the client's message.
+ Option4ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
+ Option4ClientFqdn>(ex.getQuery()->getOption(DHO_FQDN));
+
+ LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_CLIENT_FQDN_DATA)
+ .arg(ex.getQuery()->getLabel())
+ .arg(fqdn->toText());
+
+ // Create the DHCPv4 Client FQDN Option to be included in the server's
+ // response to a client.
+ Option4ClientFqdnPtr fqdn_resp(new Option4ClientFqdn(*fqdn));
+
+ // Set the server S, N, and O flags based on client's flags and
+ // current configuration.
+ D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
+ d2_mgr.adjustFqdnFlags<Option4ClientFqdn>(*fqdn, *fqdn_resp,
+ *(ex.getContext()->getDdnsParams()));
+ // Carry over the client's E flag.
+ fqdn_resp->setFlag(Option4ClientFqdn::FLAG_E,
+ fqdn->getFlag(Option4ClientFqdn::FLAG_E));
+
+ if (ex.getContext()->currentHost() &&
+ !ex.getContext()->currentHost()->getHostname().empty()) {
+ D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
+ fqdn_resp->setDomainName(d2_mgr.qualifyName(ex.getContext()->currentHost()->getHostname(),
+ *(ex.getContext()->getDdnsParams()), true),
+ Option4ClientFqdn::FULL);
+
+ } else {
+ // Adjust the domain name based on domain name value and type sent by the
+ // client and current configuration.
+ d2_mgr.adjustDomainName<Option4ClientFqdn>(*fqdn, *fqdn_resp,
+ *(ex.getContext()->getDdnsParams()));
+ }
+
+ // Add FQDN option to the response message. Note that, there may be some
+ // cases when server may choose not to include the FQDN option in a
+ // response to a client. In such cases, the FQDN should be removed from the
+ // outgoing message. In theory we could cease to include the FQDN option
+ // in this function until it is confirmed that it should be included.
+ // However, we include it here for simplicity. Functions used to acquire
+ // lease for a client will scan the response message for FQDN and if it
+ // is found they will take necessary actions to store the FQDN information
+ // in the lease database as well as to generate NameChangeRequests to DNS.
+ // If we don't store the option in the response message, we will have to
+ // propagate it in the different way to the functions which acquire the
+ // lease. This would require modifications to the API of this class.
+ LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_RESPONSE_FQDN_DATA)
+ .arg(ex.getQuery()->getLabel())
+ .arg(fqdn_resp->toText());
+ ex.getResponse()->addOption(fqdn_resp);
+}
+
+void
+Dhcpv4Srv::processHostnameOption(Dhcpv4Exchange& ex) {
+ // Fetch D2 configuration.
+ D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
+
+ // Obtain the Hostname option from the client's message.
+ OptionStringPtr opt_hostname = boost::dynamic_pointer_cast<OptionString>
+ (ex.getQuery()->getOption(DHO_HOST_NAME));
+
+ if (opt_hostname) {
+ LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_CLIENT_HOSTNAME_DATA)
+ .arg(ex.getQuery()->getLabel())
+ .arg(opt_hostname->getValue());
+ }
+
+ AllocEngine::ClientContext4Ptr ctx = ex.getContext();
+
+ // Hostname reservations take precedence over any other configuration,
+ // i.e. DDNS configuration. If we have a reserved hostname we should
+ // use it and send it back.
+ if (ctx->currentHost() && !ctx->currentHost()->getHostname().empty()) {
+ // Qualify if there is a suffix configured.
+ std::string hostname = d2_mgr.qualifyName(ctx->currentHost()->getHostname(),
+ *(ex.getContext()->getDdnsParams()), false);
+ // Convert it to lower case.
+ boost::algorithm::to_lower(hostname);
+ LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_RESERVED_HOSTNAME_ASSIGNED)
+ .arg(ex.getQuery()->getLabel())
+ .arg(hostname);
+
+ // Add it to the response
+ OptionStringPtr opt_hostname_resp(new OptionString(Option::V4, DHO_HOST_NAME, hostname));
+ ex.getResponse()->addOption(opt_hostname_resp);
+
+ // We're done here.
+ return;
+ }
+
+ // There is no reservation for this client however there is still a
+ // possibility that we'll have to send hostname option to this client
+ // if the client has included hostname option or the configuration of
+ // the server requires that we send the option regardless.
+ D2ClientConfig::ReplaceClientNameMode replace_name_mode =
+ ex.getContext()->getDdnsParams()->getReplaceClientNameMode();
+
+ // If we don't have a hostname then either we'll supply it or do nothing.
+ if (!opt_hostname) {
+ // If we're configured to supply it then add it to the response.
+ // Use the root domain to signal later on that we should replace it.
+ if (replace_name_mode == D2ClientConfig::RCM_ALWAYS ||
+ replace_name_mode == D2ClientConfig::RCM_WHEN_NOT_PRESENT) {
+ LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL_DATA,
+ DHCP4_GENERATE_FQDN)
+ .arg(ex.getQuery()->getLabel());
+ OptionStringPtr opt_hostname_resp(new OptionString(Option::V4,
+ DHO_HOST_NAME,
+ "."));
+ ex.getResponse()->addOption(opt_hostname_resp);
+ }
+
+ return;
+ }
+
+ // Client sent us a hostname option so figure out what to do with it.
+ LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_CLIENT_HOSTNAME_DATA)
+ .arg(ex.getQuery()->getLabel())
+ .arg(opt_hostname->getValue());
+
+ std::string hostname = isc::util::str::trim(opt_hostname->getValue());
+ unsigned int label_count;
+
+ try {
+ // Parsing into labels can throw on malformed content so we're
+ // going to explicitly catch that here.
+ label_count = OptionDataTypeUtil::getLabelCount(hostname);
+ } catch (const std::exception& exc) {
+ LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL, DHCP4_CLIENT_HOSTNAME_MALFORMED)
+ .arg(ex.getQuery()->getLabel())
+ .arg(exc.what());
+ return;
+ }
+
+ // The hostname option sent by the client should be at least 1 octet long.
+ // If it isn't we ignore this option. (Per RFC 2131, section 3.14)
+ /// @todo It would be more liberal to accept this and let it fall into
+ /// the case of replace or less than two below.
+ if (label_count == 0) {
+ LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL, DHCP4_EMPTY_HOSTNAME)
+ .arg(ex.getQuery()->getLabel());
+ return;
+ }
+
+ // Stores the value we eventually use, so we can send it back.
+ OptionStringPtr opt_hostname_resp;
+
+ // The hostname option may be unqualified or fully qualified. The lab_count
+ // holds the number of labels for the name. The number of 1 means that
+ // there is only root label "." (even for unqualified names, as the
+ // getLabelCount function treats each name as a fully qualified one).
+ // By checking the number of labels present in the hostname we may infer
+ // whether client has sent the fully qualified or unqualified hostname.
+
+ if ((replace_name_mode == D2ClientConfig::RCM_ALWAYS ||
+ replace_name_mode == D2ClientConfig::RCM_WHEN_PRESENT)
+ || label_count < 2) {
+ // Set to root domain to signal later on that we should replace it.
+ // DHO_HOST_NAME is a string option which cannot be empty.
+ /// @todo We may want to reconsider whether it is appropriate for the
+ /// client to send a root domain name as a Hostname. There are
+ /// also extensions to the auto generation of the client's name,
+ /// e.g. conversion to the puny code which may be considered at some
+ /// point.
+ /// For now, we just remain liberal and expect that the DNS will handle
+ /// conversion if needed and possible.
+ opt_hostname_resp.reset(new OptionString(Option::V4, DHO_HOST_NAME, "."));
+ } else {
+ // Sanitize the name the client sent us, if we're configured to do so.
+ isc::util::str::StringSanitizerPtr sanitizer =
+ ex.getContext()->getDdnsParams()->getHostnameSanitizer();
+
+ if (sanitizer) {
+ hostname = sanitizer->scrub(hostname);
+ }
+
+ // Convert hostname to lower case.
+ boost::algorithm::to_lower(hostname);
+
+ if (label_count == 2) {
+ // If there are two labels, it means that the client has specified
+ // the unqualified name. We have to concatenate the unqualified name
+ // with the domain name. The false value passed as a second argument
+ // indicates that the trailing dot should not be appended to the
+ // hostname. We don't want to append the trailing dot because
+ // we don't know whether the hostname is partial or not and some
+ // clients do not handle the hostnames with the trailing dot.
+ opt_hostname_resp.reset(
+ new OptionString(Option::V4, DHO_HOST_NAME,
+ d2_mgr.qualifyName(hostname, *(ex.getContext()->getDdnsParams()),
+ false)));
+ } else {
+ opt_hostname_resp.reset(new OptionString(Option::V4, DHO_HOST_NAME, hostname));
+ }
+ }
+
+ LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_RESPONSE_HOSTNAME_DATA)
+ .arg(ex.getQuery()->getLabel())
+ .arg(opt_hostname_resp->getValue());
+ ex.getResponse()->addOption(opt_hostname_resp);
+}
+
+void
+Dhcpv4Srv::createNameChangeRequests(const Lease4Ptr& lease,
+ const Lease4Ptr& old_lease,
+ const DdnsParams& ddns_params) {
+ if (!lease) {
+ isc_throw(isc::Unexpected,
+ "NULL lease specified when creating NameChangeRequest");
+ }
+
+ // Nothing to do if updates are not enabled.
+ if (!ddns_params.getEnableUpdates()) {
+ return;
+ }
+
+ if (!old_lease || ddns_params.getUpdateOnRenew() || !lease->hasIdenticalFqdn(*old_lease)) {
+ if (old_lease) {
+ // Queue's up a remove of the old lease's DNS (if needed)
+ queueNCR(CHG_REMOVE, old_lease);
+ }
+
+ // We may need to generate the NameChangeRequest for the new lease. It
+ // will be generated only if hostname is set and if forward or reverse
+ // update has been requested.
+ queueNCR(CHG_ADD, lease);
+ }
+}
+
+void
+Dhcpv4Srv::assignLease(Dhcpv4Exchange& ex) {
+ // Get the pointers to the query and the response messages.
+ Pkt4Ptr query = ex.getQuery();
+ Pkt4Ptr resp = ex.getResponse();
+
+ // Get the context.
+ AllocEngine::ClientContext4Ptr ctx = ex.getContext();
+
+ // Subnet should have been already selected when the context was created.
+ Subnet4Ptr subnet = ctx->subnet_;
+ if (!subnet) {
+ // This particular client is out of luck today. We do not have
+ // information about the subnet he is connected to. This likely means
+ // misconfiguration of the server (or some relays).
+
+ // Perhaps this should be logged on some higher level?
+ LOG_ERROR(bad_packet4_logger, DHCP4_PACKET_NAK_0001)
+ .arg(query->getLabel())
+ .arg(query->getRemoteAddr().toText())
+ .arg(query->getName());
+ resp->setType(DHCPNAK);
+ resp->setYiaddr(IOAddress::IPV4_ZERO_ADDRESS());
+ return;
+ }
+
+ // Get the server identifier. It will be used to determine the state
+ // of the client.
+ OptionCustomPtr opt_serverid = boost::dynamic_pointer_cast<
+ OptionCustom>(query->getOption(DHO_DHCP_SERVER_IDENTIFIER));
+
+ // Check if the client has sent a requested IP address option or
+ // ciaddr.
+ OptionCustomPtr opt_requested_address = boost::dynamic_pointer_cast<
+ OptionCustom>(query->getOption(DHO_DHCP_REQUESTED_ADDRESS));
+ IOAddress hint(IOAddress::IPV4_ZERO_ADDRESS());
+ if (opt_requested_address) {
+ hint = opt_requested_address->readAddress();
+
+ } else if (!query->getCiaddr().isV4Zero()) {
+ hint = query->getCiaddr();
+
+ }
+
+ HWAddrPtr hwaddr = query->getHWAddr();
+
+ // "Fake" allocation is processing of DISCOVER message. We pretend to do an
+ // allocation, but we do not put the lease in the database. That is ok,
+ // because we do not guarantee that the user will get that exact lease. If
+ // the user selects this server to do actual allocation (i.e. sends REQUEST)
+ // it should include this hint. That will help us during the actual lease
+ // allocation.
+ bool fake_allocation = (query->getType() == DHCPDISCOVER);
+ Subnet4Ptr original_subnet = subnet;
+
+ // Get client-id. It is not mandatory in DHCPv4.
+ ClientIdPtr client_id = ex.getContext()->clientid_;
+
+ // If there is no server id and there is a Requested IP Address option
+ // the client is in the INIT-REBOOT state in which the server has to
+ // determine whether the client's notion of the address is correct
+ // and whether the client is known, i.e., has a lease.
+ if (!fake_allocation && !opt_serverid && opt_requested_address) {
+
+ LOG_INFO(lease4_logger, DHCP4_INIT_REBOOT)
+ .arg(query->getLabel())
+ .arg(hint.toText());
+
+ Lease4Ptr lease;
+
+ // We used to issue a separate query (two actually: one for client-id
+ // and another one for hw-addr for) each subnet in the shared network.
+ // That was horribly inefficient if the client didn't have any lease
+ // (or there were many subnets and the client happened to be in one
+ // of the last subnets).
+ //
+ // We now issue at most two queries: get all the leases for specific
+ // client-id and then get all leases for specific hw-address.
+ if (client_id) {
+
+ // Get all the leases for this client-id
+ Lease4Collection leases_client_id = LeaseMgrFactory::instance().getLease4(*client_id);
+ if (!leases_client_id.empty()) {
+ Subnet4Ptr s = original_subnet;
+
+ // Among those returned try to find a lease that belongs to
+ // current shared network.
+ while (s) {
+ for (auto l = leases_client_id.begin(); l != leases_client_id.end(); ++l) {
+ if ((*l)->subnet_id_ == s->getID()) {
+ lease = *l;
+ break;
+ }
+ }
+
+ if (lease) {
+ break;
+
+ } else {
+ s = s->getNextSubnet(original_subnet, query->getClasses());
+ }
+ }
+ }
+ }
+
+ // If we haven't found a lease yet, try again by hardware-address.
+ // The logic is the same.
+ if (!lease && hwaddr) {
+
+ // Get all leases for this particular hw-address.
+ Lease4Collection leases_hwaddr = LeaseMgrFactory::instance().getLease4(*hwaddr);
+ if (!leases_hwaddr.empty()) {
+ Subnet4Ptr s = original_subnet;
+
+ // Pick one that belongs to a subnet in this shared network.
+ while (s) {
+ for (auto l = leases_hwaddr.begin(); l != leases_hwaddr.end(); ++l) {
+ if ((*l)->subnet_id_ == s->getID()) {
+ lease = *l;
+ break;
+ }
+ }
+
+ if (lease) {
+ break;
+
+ } else {
+ s = s->getNextSubnet(original_subnet, query->getClasses());
+ }
+ }
+ }
+ }
+
+ // Check the first error case: unknown client. We check this before
+ // validating the address sent because we don't want to respond if
+ // we don't know this client, except if we're authoritative.
+ bool authoritative = original_subnet->getAuthoritative();
+ bool known_client = lease && lease->belongsToClient(hwaddr, client_id);
+ if (!authoritative && !known_client) {
+ LOG_DEBUG(bad_packet4_logger, DBG_DHCP4_DETAIL,
+ DHCP4_NO_LEASE_INIT_REBOOT)
+ .arg(query->getLabel())
+ .arg(hint.toText());
+
+ ex.deleteResponse();
+ return;
+ }
+
+ // If we know this client, check if his notion of the IP address is
+ // correct, if we don't know him, check if we are authoritative.
+ if ((known_client && (lease->addr_ != hint)) ||
+ (!known_client && authoritative)) {
+ LOG_DEBUG(bad_packet4_logger, DBG_DHCP4_DETAIL,
+ DHCP4_PACKET_NAK_0002)
+ .arg(query->getLabel())
+ .arg(hint.toText());
+
+ resp->setType(DHCPNAK);
+ resp->setYiaddr(IOAddress::IPV4_ZERO_ADDRESS());
+ return;
+ }
+ }
+
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+ // We need to set these values in the context as they haven't been set yet.
+ ctx->requested_address_ = hint;
+ ctx->fake_allocation_ = fake_allocation;
+ ctx->callout_handle_ = callout_handle;
+
+ // If client query contains an FQDN or Hostname option, server
+ // should respond to the client with the appropriate FQDN or Hostname
+ // option to indicate if it takes responsibility for the DNS updates.
+ // This is also the source for the hostname and dns flags that are
+ // initially added to the lease. In most cases, this information is
+ // good now. If we end up changing subnets in allocation we'll have to
+ // do it again and then update the lease.
+ processClientName(ex);
+
+ // Get a lease.
+ Lease4Ptr lease = alloc_engine_->allocateLease4(*ctx);
+
+ // Tracks whether or not the client name (FQDN or host) has changed since
+ // the lease was allocated.
+ bool client_name_changed = false;
+
+ // Subnet may be modified by the allocation engine, if the initial subnet
+ // belongs to a shared network.
+ if (subnet && ctx->subnet_ && subnet->getID() != ctx->subnet_->getID()) {
+ SharedNetwork4Ptr network;
+ subnet->getSharedNetwork(network);
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC_DATA, DHCP4_SUBNET_DYNAMICALLY_CHANGED)
+ .arg(query->getLabel())
+ .arg(subnet->toText())
+ .arg(ctx->subnet_->toText())
+ .arg(network ? network->getName() : "<no network?>");
+
+ subnet = ctx->subnet_;
+
+ if (lease) {
+ // We changed subnets and that means DDNS parameters might be different
+ // so we need to rerun client name processing logic. Arguably we could
+ // compare DDNS parameters for both subnets and then decide if we need
+ // to rerun the name logic, but that's not likely to be any faster than
+ // just re-running the name logic. @todo When inherited parameter
+ // performance is improved this argument could be revisited.
+ // Another case is the new subnet has a reserved hostname.
+
+ // First, we need to remove the prior values from the response and reset
+ // those in context, to give processClientName a clean slate.
+ resp->delOption(DHO_FQDN);
+ resp->delOption(DHO_HOST_NAME);
+ ctx->hostname_ = "";
+ ctx->fwd_dns_update_ = false;
+ ctx->rev_dns_update_ = false;
+
+ // Regenerate the name and dns flags.
+ processClientName(ex);
+
+ // If the results are different from the values already on the
+ // lease, flag it so the lease gets updated down below.
+ if ((lease->hostname_ != ctx->hostname_) ||
+ (lease->fqdn_fwd_ != ctx->fwd_dns_update_) ||
+ (lease->fqdn_rev_ != ctx->rev_dns_update_)) {
+ lease->hostname_ = ctx->hostname_;
+ lease->fqdn_fwd_ = ctx->fwd_dns_update_;
+ lease->fqdn_rev_ = ctx->rev_dns_update_;
+ client_name_changed = true;
+ }
+ }
+ }
+
+ if (lease) {
+ // We have a lease! Let's set it in the packet and send it back to
+ // the client.
+ if (fake_allocation) {
+ LOG_INFO(lease4_logger, DHCP4_LEASE_ADVERT)
+ .arg(query->getLabel())
+ .arg(lease->addr_.toText());
+ } else {
+ LOG_INFO(lease4_logger, DHCP4_LEASE_ALLOC)
+ .arg(query->getLabel())
+ .arg(lease->addr_.toText())
+ .arg(Lease::lifetimeToText(lease->valid_lft_));
+ }
+
+ // We're logging this here, because this is the place where we know
+ // which subnet has been actually used for allocation. If the
+ // client identifier matching is disabled, we want to make sure that
+ // the user is notified.
+ if (!ctx->subnet_->getMatchClientId()) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_CLIENTID_IGNORED_FOR_LEASES)
+ .arg(ctx->query_->getLabel())
+ .arg(ctx->subnet_->getID());
+ }
+
+ resp->setYiaddr(lease->addr_);
+
+ /// @todo The server should check what ciaddr the client has supplied
+ /// in ciaddr. Currently the ciaddr is ignored except for the subnet
+ /// selection. If the client supplied an invalid address, the server
+ /// will also return an invalid address here.
+ if (!fake_allocation) {
+ // If this is a renewing client it will set a ciaddr which the
+ // server may include in the response. If this is a new allocation
+ // the client will set ciaddr to 0 and this will also be propagated
+ // to the server's resp.
+ resp->setCiaddr(query->getCiaddr());
+ }
+
+ // We may need to update FQDN or hostname if the server is to generate
+ // a new name from the allocated IP address or if the allocation engine
+ // switched to a different subnet within a shared network.
+ postAllocateNameUpdate(ctx, lease, query, resp, client_name_changed);
+
+ // Reuse the lease if possible.
+ if (lease->reuseable_valid_lft_ > 0) {
+ lease->valid_lft_ = lease->reuseable_valid_lft_;
+ LOG_INFO(lease4_logger, DHCP4_LEASE_REUSE)
+ .arg(query->getLabel())
+ .arg(lease->addr_.toText())
+ .arg(Lease::lifetimeToText(lease->valid_lft_));
+ }
+
+ // IP Address Lease time (type 51)
+ OptionPtr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME,
+ lease->valid_lft_));
+ resp->addOption(opt);
+
+ // Subnet mask (type 1)
+ resp->addOption(getNetmaskOption(subnet));
+
+ // Set T1 and T2 per configuration.
+ setTeeTimes(lease, subnet, resp);
+
+ // Create NameChangeRequests if this is a real allocation.
+ if (!fake_allocation) {
+ try {
+ createNameChangeRequests(lease, ctx->old_lease_,
+ *ex.getContext()->getDdnsParams());
+ } catch (const Exception& ex) {
+ LOG_ERROR(ddns4_logger, DHCP4_NCR_CREATION_FAILED)
+ .arg(query->getLabel())
+ .arg(ex.what());
+ }
+ }
+
+ } else {
+ // Allocation engine did not allocate a lease. The engine logged
+ // cause of that failure.
+ if (ctx->unknown_requested_addr_) {
+ Subnet4Ptr s = original_subnet;
+ // Address might have been rejected via class guard (i.e. not
+ // allowed for this client). We need to determine if we truly
+ // do not know about the address or whether this client just
+ // isn't allowed to have that address. We should only DHCPNAK
+ // For the latter.
+ while (s) {
+ if (s->inPool(Lease::TYPE_V4, hint)) {
+ break;
+ }
+
+ s = s->getNextSubnet(original_subnet);
+ }
+
+ // If we didn't find a subnet, it's not an address we know about
+ // so we drop the DHCPNAK.
+ if (!s) {
+ LOG_DEBUG(bad_packet4_logger, DBG_DHCP4_DETAIL,
+ DHCP4_UNKNOWN_ADDRESS_REQUESTED)
+ .arg(query->getLabel())
+ .arg(query->getCiaddr().toText())
+ .arg(opt_requested_address ?
+ opt_requested_address->readAddress().toText() : "(no address)");
+ ex.deleteResponse();
+ return;
+ }
+ }
+
+ LOG_DEBUG(bad_packet4_logger, DBG_DHCP4_DETAIL, fake_allocation ?
+ DHCP4_PACKET_NAK_0003 : DHCP4_PACKET_NAK_0004)
+ .arg(query->getLabel())
+ .arg(query->getCiaddr().toText())
+ .arg(opt_requested_address ?
+ opt_requested_address->readAddress().toText() : "(no address)");
+
+ resp->setType(DHCPNAK);
+ resp->setYiaddr(IOAddress::IPV4_ZERO_ADDRESS());
+
+ resp->delOption(DHO_FQDN);
+ resp->delOption(DHO_HOST_NAME);
+ }
+}
+
+void
+Dhcpv4Srv::postAllocateNameUpdate(const AllocEngine::ClientContext4Ptr& ctx, const Lease4Ptr& lease,
+ const Pkt4Ptr& query, const Pkt4Ptr& resp, bool client_name_changed) {
+ // We may need to update FQDN or hostname if the server is to generate
+ // new name from the allocated IP address or if the allocation engine
+ // has switched to a different subnet within a shared network. Get
+ // FQDN and hostname options from the response.
+ OptionStringPtr opt_hostname;
+ Option4ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
+ Option4ClientFqdn>(resp->getOption(DHO_FQDN));
+ if (!fqdn) {
+ opt_hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
+ if (!opt_hostname) {
+ // We don't have either one, nothing to do.
+ return;
+ }
+ }
+
+ // Empty hostname on the lease means we need to generate it.
+ if (lease->hostname_.empty()) {
+ // Note that if we have received the hostname option, rather than
+ // Client FQDN the trailing dot is not appended to the generated
+ // hostname because some clients don't handle the trailing dot in
+ // the hostname. Whether the trailing dot is appended or not is
+ // controlled by the second argument to the generateFqdn().
+ lease->hostname_ = CfgMgr::instance().getD2ClientMgr()
+ .generateFqdn(lease->addr_, *(ctx->getDdnsParams()), static_cast<bool>(fqdn));
+
+ LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL, DHCP4_RESPONSE_HOSTNAME_GENERATE)
+ .arg(query->getLabel())
+ .arg(lease->hostname_);
+
+ client_name_changed = true;
+ }
+
+ if (client_name_changed) {
+ // The operations below are rather safe, but we want to catch
+ // any potential exceptions (e.g. invalid lease database backend
+ // implementation) and log an error.
+ try {
+ if (!ctx->fake_allocation_) {
+ // The lease can't be reused.
+ lease->reuseable_valid_lft_ = 0;
+
+ // The lease update should be safe, because the lease should
+ // be already in the database. In most cases the exception
+ // would be thrown if the lease was missing.
+ LeaseMgrFactory::instance().updateLease4(lease);
+ }
+
+ // The name update in the outbound option should be also safe,
+ // because the generated name is well formed.
+ if (fqdn) {
+ fqdn->setDomainName(lease->hostname_, Option4ClientFqdn::FULL);
+ } else {
+ opt_hostname->setValue(lease->hostname_);
+ }
+ } catch (const Exception& ex) {
+ LOG_ERROR(ddns4_logger, DHCP4_POST_ALLOCATION_NAME_UPDATE_FAIL)
+ .arg(query->getLabel())
+ .arg(lease->hostname_)
+ .arg(ex.what());
+ }
+ }
+}
+
+/// @todo This logic to be modified if we decide to support infinite lease times.
+void
+Dhcpv4Srv::setTeeTimes(const Lease4Ptr& lease, const Subnet4Ptr& subnet, Pkt4Ptr resp) {
+
+ uint32_t t2_time = 0;
+ // If T2 is explicitly configured we'll use try value.
+ if (!subnet->getT2().unspecified()) {
+ t2_time = subnet->getT2();
+ } else if (subnet->getCalculateTeeTimes()) {
+ // Calculating tee times is enabled, so calculated it.
+ t2_time = static_cast<uint32_t>(round(subnet->getT2Percent() * (lease->valid_lft_)));
+ }
+
+ // Send the T2 candidate value only if it's sane: to be sane it must be less than
+ // the valid life time.
+ uint32_t timer_ceiling = lease->valid_lft_;
+ if (t2_time > 0 && t2_time < timer_ceiling) {
+ OptionUint32Ptr t2(new OptionUint32(Option::V4, DHO_DHCP_REBINDING_TIME, t2_time));
+ resp->addOption(t2);
+ // When we send T2, timer ceiling for T1 becomes T2.
+ timer_ceiling = t2_time;
+ }
+
+ uint32_t t1_time = 0;
+ // If T1 is explicitly configured we'll use try value.
+ if (!subnet->getT1().unspecified()) {
+ t1_time = subnet->getT1();
+ } else if (subnet->getCalculateTeeTimes()) {
+ // Calculating tee times is enabled, so calculate it.
+ t1_time = static_cast<uint32_t>(round(subnet->getT1Percent() * (lease->valid_lft_)));
+ }
+
+ // Send T1 if it's sane: If we sent T2, T1 must be less than that. If not it must be
+ // less than the valid life time.
+ if (t1_time > 0 && t1_time < timer_ceiling) {
+ OptionUint32Ptr t1(new OptionUint32(Option::V4, DHO_DHCP_RENEWAL_TIME, t1_time));
+ resp->addOption(t1);
+ }
+}
+
+uint16_t
+Dhcpv4Srv::checkRelayPort(const Dhcpv4Exchange& ex) {
+
+ // Look for a relay-port RAI sub-option in the query.
+ const Pkt4Ptr& query = ex.getQuery();
+ const OptionPtr& rai = query->getOption(DHO_DHCP_AGENT_OPTIONS);
+ if (rai && rai->getOption(RAI_OPTION_RELAY_PORT)) {
+ // Got the sub-option so use the remote port set by the relay.
+ return (query->getRemotePort());
+ }
+ return (0);
+}
+
+void
+Dhcpv4Srv::adjustIfaceData(Dhcpv4Exchange& ex) {
+ adjustRemoteAddr(ex);
+
+ // Initialize the pointers to the client's message and the server's
+ // response.
+ Pkt4Ptr query = ex.getQuery();
+ Pkt4Ptr response = ex.getResponse();
+
+ // The DHCPINFORM is generally unicast to the client. The only situation
+ // when the server is unable to unicast to the client is when the client
+ // doesn't include ciaddr and the message is relayed. In this case the
+ // server has to reply via relay agent. For other messages we send back
+ // through relay if message is relayed, and unicast to the client if the
+ // message is not relayed.
+ // If client port was set from the command line enforce all responses
+ // to it. Of course it is only for testing purposes.
+ // Note that the call to this function may throw if invalid combination
+ // of hops and giaddr is found (hops = 0 if giaddr = 0 and hops != 0 if
+ // giaddr != 0). The exception will propagate down and eventually cause the
+ // packet to be discarded.
+ if (client_port_) {
+ response->setRemotePort(client_port_);
+ } else if (((query->getType() == DHCPINFORM) &&
+ ((!query->getCiaddr().isV4Zero()) ||
+ (!query->isRelayed() && !query->getRemoteAddr().isV4Zero()))) ||
+ ((query->getType() != DHCPINFORM) && !query->isRelayed())) {
+ response->setRemotePort(DHCP4_CLIENT_PORT);
+
+ } else {
+ // RFC 8357 section 5.1
+ uint16_t relay_port = checkRelayPort(ex);
+ response->setRemotePort(relay_port ? relay_port : DHCP4_SERVER_PORT);
+ }
+
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getCurrentCfg()->getCfgIface();
+ if (query->isRelayed() &&
+ (cfg_iface->getSocketType() == CfgIface::SOCKET_UDP) &&
+ (cfg_iface->getOutboundIface() == CfgIface::USE_ROUTING)) {
+
+ // Mark the response to follow routing
+ response->setLocalAddr(IOAddress::IPV4_ZERO_ADDRESS());
+ response->resetIndex();
+ // But keep the interface name
+ response->setIface(query->getIface());
+
+ } else {
+
+ IOAddress local_addr = query->getLocalAddr();
+
+ // In many cases the query is sent to a broadcast address. This address
+ // appears as a local address in the query message. We can't simply copy
+ // this address to a response message and use it as a source address.
+ // Instead we will need to use the address assigned to the interface
+ // on which the query has been received. In other cases, we will just
+ // use this address as a source address for the response.
+ // Do the same for DHCPv4-over-DHCPv6 exchanges.
+ if (local_addr.isV4Bcast() || query->isDhcp4o6()) {
+ local_addr = IfaceMgr::instance().getSocket(query).addr_;
+ }
+
+ // We assume that there is an appropriate socket bound to this address
+ // and that the address is correct. This is safe assumption because
+ // the local address of the query is set when the query is received.
+ // The query sent to an incorrect address wouldn't have been received.
+ // However, if socket is closed for this address between the reception
+ // of the query and sending a response, the IfaceMgr should detect it
+ // and return an error.
+ response->setLocalAddr(local_addr);
+ // In many cases the query is sent to a broadcast address. This address
+ // appears as a local address in the query message. Therefore we can't
+ // simply copy local address from the query and use it as a source
+ // address for the response. Instead, we have to check what address our
+ // socket is bound to and use it as a source address. This operation
+ // may throw if for some reason the socket is closed.
+ /// @todo Consider an optimization that we use local address from
+ /// the query if this address is not broadcast.
+ response->setIndex(query->getIndex());
+ response->setIface(query->getIface());
+ }
+
+ if (server_port_) {
+ response->setLocalPort(server_port_);
+ } else {
+ response->setLocalPort(DHCP4_SERVER_PORT);
+ }
+}
+
+void
+Dhcpv4Srv::adjustRemoteAddr(Dhcpv4Exchange& ex) {
+ // Initialize the pointers to the client's message and the server's
+ // response.
+ Pkt4Ptr query = ex.getQuery();
+ Pkt4Ptr response = ex.getResponse();
+
+ // DHCPv4-over-DHCPv6 is simple
+ if (query->isDhcp4o6()) {
+ response->setRemoteAddr(query->getRemoteAddr());
+ return;
+ }
+
+ // The DHCPINFORM is slightly different than other messages in a sense
+ // that the server should always unicast the response to the ciaddr.
+ // It appears however that some clients don't set the ciaddr. We still
+ // want to provision these clients and we do what we can't to send the
+ // packet to the address where client can receive it.
+ if (query->getType() == DHCPINFORM) {
+ // If client adheres to RFC2131 it will set the ciaddr and in this
+ // case we always unicast our response to this address.
+ if (!query->getCiaddr().isV4Zero()) {
+ response->setRemoteAddr(query->getCiaddr());
+
+ // If we received DHCPINFORM via relay and the ciaddr is not set we
+ // will try to send the response via relay. The caveat is that the
+ // relay will not have any idea where to forward the packet because
+ // the yiaddr is likely not set. So, the broadcast flag is set so
+ // as the response may be broadcast.
+ } else if (query->isRelayed()) {
+ response->setRemoteAddr(query->getGiaddr());
+ response->setFlags(response->getFlags() | BOOTP_BROADCAST);
+
+ // If there is no ciaddr and no giaddr the only thing we can do is
+ // to use the source address of the packet.
+ } else {
+ response->setRemoteAddr(query->getRemoteAddr());
+ }
+ // Remote address is now set so return.
+ return;
+ }
+
+ // If received relayed message, server responds to the relay address.
+ if (query->isRelayed()) {
+ // The client should set the ciaddr when sending the DHCPINFORM
+ // but in case he didn't, the relay may not be able to determine the
+ // address of the client, because yiaddr is not set when responding
+ // to Confirm and the only address available was the source address
+ // of the client. The source address is however not used here because
+ // the message is relayed. Therefore, we set the BROADCAST flag so
+ // as the relay can broadcast the packet.
+ if ((query->getType() == DHCPINFORM) &&
+ query->getCiaddr().isV4Zero()) {
+ response->setFlags(BOOTP_BROADCAST);
+ }
+ response->setRemoteAddr(query->getGiaddr());
+
+ // If giaddr is 0 but client set ciaddr, server should unicast the
+ // response to ciaddr.
+ } else if (!query->getCiaddr().isV4Zero()) {
+ response->setRemoteAddr(query->getCiaddr());
+
+ // We can't unicast the response to the client when sending DHCPNAK,
+ // because we haven't allocated address for him. Therefore,
+ // DHCPNAK is broadcast.
+ } else if (response->getType() == DHCPNAK) {
+ response->setRemoteAddr(IOAddress::IPV4_BCAST_ADDRESS());
+
+ // If yiaddr is set it means that we have created a lease for a client.
+ } else if (!response->getYiaddr().isV4Zero()) {
+ // If the broadcast bit is set in the flags field, we have to
+ // send the response to broadcast address. Client may have requested it
+ // because it doesn't support reception of messages on the interface
+ // which doesn't have an address assigned. The other case when response
+ // must be broadcasted is when our server does not support responding
+ // directly to a client without address assigned.
+ const bool bcast_flag = ((query->getFlags() & Pkt4::FLAG_BROADCAST_MASK) != 0);
+ if (!IfaceMgr::instance().isDirectResponseSupported() || bcast_flag) {
+ response->setRemoteAddr(IOAddress::IPV4_BCAST_ADDRESS());
+
+ // Client cleared the broadcast bit and we support direct responses
+ // so we should unicast the response to a newly allocated address -
+ // yiaddr.
+ } else {
+ response->setRemoteAddr(response ->getYiaddr());
+
+ }
+
+ // In most cases, we should have the remote address found already. If we
+ // found ourselves at this point, the rational thing to do is to respond
+ // to the address we got the query from.
+ } else {
+ response->setRemoteAddr(query->getRemoteAddr());
+ }
+
+ // For testing *only*.
+ if (getSendResponsesToSource()) {
+ response->setRemoteAddr(query->getRemoteAddr());
+ }
+}
+
+void
+Dhcpv4Srv::setFixedFields(Dhcpv4Exchange& ex) {
+ Pkt4Ptr query = ex.getQuery();
+ Pkt4Ptr response = ex.getResponse();
+
+ // Step 1: Start with fixed fields defined on subnet level.
+ Subnet4Ptr subnet = ex.getContext()->subnet_;
+ if (subnet) {
+ IOAddress subnet_next_server = subnet->getSiaddr();
+ if (!subnet_next_server.isV4Zero()) {
+ response->setSiaddr(subnet_next_server);
+ }
+
+ const string& sname = subnet->getSname();
+ if (!sname.empty()) {
+ // Converting string to (const uint8_t*, size_t len) format is
+ // tricky. reinterpret_cast is not the most elegant solution,
+ // but it does avoid us making unnecessary copy. We will convert
+ // sname and file fields in Pkt4 to string one day and life
+ // will be easier.
+ response->setSname(reinterpret_cast<const uint8_t*>(sname.c_str()),
+ sname.size());
+ }
+
+ const string& filename = subnet->getFilename();
+ if (!filename.empty()) {
+ // Converting string to (const uint8_t*, size_t len) format is
+ // tricky. reinterpret_cast is not the most elegant solution,
+ // but it does avoid us making unnecessary copy. We will convert
+ // sname and file fields in Pkt4 to string one day and life
+ // will be easier.
+ response->setFile(reinterpret_cast<const uint8_t*>(filename.c_str()),
+ filename.size());
+ }
+ }
+
+ // Step 2: Try to set the values based on classes.
+ // Any values defined in classes will override those from subnet level.
+ const ClientClasses classes = query->getClasses();
+ if (!classes.empty()) {
+
+ // Let's get class definitions
+ const ClientClassDictionaryPtr& dict =
+ CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
+
+ // Now we need to iterate over the classes assigned to the
+ // query packet and find corresponding class definitions for it.
+ // We want the first value found for each field. We track how
+ // many we've found so we can stop if we have all three.
+ IOAddress next_server = IOAddress::IPV4_ZERO_ADDRESS();
+ string sname;
+ string filename;
+ size_t found_cnt = 0; // How many fields we have found.
+ for (ClientClasses::const_iterator name = classes.cbegin();
+ name != classes.cend() && found_cnt < 3; ++name) {
+
+ ClientClassDefPtr cl = dict->findClass(*name);
+ if (!cl) {
+ // Let's skip classes that don't have definitions. Currently
+ // these are automatic classes VENDOR_CLASS_something, but there
+ // may be other classes assigned under other circumstances, e.g.
+ // by hooks.
+ continue;
+ }
+
+ if (next_server == IOAddress::IPV4_ZERO_ADDRESS()) {
+ next_server = cl->getNextServer();
+ if (!next_server.isV4Zero()) {
+ response->setSiaddr(next_server);
+ found_cnt++;
+ }
+ }
+
+ if (sname.empty()) {
+ sname = cl->getSname();
+ if (!sname.empty()) {
+ // Converting string to (const uint8_t*, size_t len) format is
+ // tricky. reinterpret_cast is not the most elegant solution,
+ // but it does avoid us making unnecessary copy. We will convert
+ // sname and file fields in Pkt4 to string one day and life
+ // will be easier.
+ response->setSname(reinterpret_cast<const uint8_t*>(sname.c_str()),
+ sname.size());
+ found_cnt++;
+ }
+ }
+
+ if (filename.empty()) {
+ filename = cl->getFilename();
+ if (!filename.empty()) {
+ // Converting string to (const uint8_t*, size_t len) format is
+ // tricky. reinterpret_cast is not the most elegant solution,
+ // but it does avoid us making unnecessary copy. We will convert
+ // sname and file fields in Pkt4 to string one day and life
+ // will be easier.
+ response->setFile(reinterpret_cast<const uint8_t*>(filename.c_str()),
+ filename.size());
+ found_cnt++;
+ }
+ }
+ }
+ }
+
+ // Step 3: try to set values using HR. Any values coming from there will override
+ // the subnet or class values.
+ ex.setReservedMessageFields();
+}
+
+OptionPtr
+Dhcpv4Srv::getNetmaskOption(const Subnet4Ptr& subnet) {
+ uint32_t netmask = getNetmask4(subnet->get().second).toUint32();
+
+ OptionPtr opt(new OptionInt<uint32_t>(Option::V4,
+ DHO_SUBNET_MASK, netmask));
+
+ return (opt);
+}
+
+Pkt4Ptr
+Dhcpv4Srv::processDiscover(Pkt4Ptr& discover, AllocEngine::ClientContext4Ptr& context) {
+ // server-id is forbidden.
+ sanityCheck(discover, FORBIDDEN);
+
+ bool drop = false;
+ Subnet4Ptr subnet = selectSubnet(discover, drop);
+
+ // Stop here if selectSubnet decided to drop the packet
+ if (drop) {
+ return (Pkt4Ptr());
+ }
+
+ Dhcpv4Exchange ex(alloc_engine_, discover, context, subnet, drop);
+
+ // Stop here if Dhcpv4Exchange constructor decided to drop the packet
+ if (drop) {
+ return (Pkt4Ptr());
+ }
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ // The lease reclamation cannot run at the same time.
+ ReadLockGuard share(alloc_engine_->getReadWriteMutex());
+
+ assignLease(ex);
+ } else {
+ assignLease(ex);
+ }
+
+ if (!ex.getResponse()) {
+ // The offer is empty so return it *now*!
+ return (Pkt4Ptr());
+ }
+
+ // Adding any other options makes sense only when we got the lease.
+ if (!ex.getResponse()->getYiaddr().isV4Zero()) {
+ // If this is global reservation or the subnet doesn't belong to a shared
+ // network we have already fetched it and evaluated the classes.
+ ex.conditionallySetReservedClientClasses();
+
+ // Required classification
+ requiredClassify(ex);
+
+ buildCfgOptionList(ex);
+ appendRequestedOptions(ex);
+ appendRequestedVendorOptions(ex);
+ // There are a few basic options that we always want to
+ // include in the response. If client did not request
+ // them we append them for him.
+ appendBasicOptions(ex);
+
+ // Set fixed fields (siaddr, sname, filename) if defined in
+ // the reservation, class or subnet specific configuration.
+ setFixedFields(ex);
+
+ } else {
+ // If the server can't offer an address, it drops the packet.
+ return (Pkt4Ptr());
+
+ }
+
+ // Set the src/dest IP address, port and interface for the outgoing
+ // packet.
+ adjustIfaceData(ex);
+
+ appendServerID(ex);
+
+ return (ex.getResponse());
+}
+
+Pkt4Ptr
+Dhcpv4Srv::processRequest(Pkt4Ptr& request, AllocEngine::ClientContext4Ptr& context) {
+ // Since we cannot distinguish between client states
+ // we'll make server-id is optional for REQUESTs.
+ sanityCheck(request, OPTIONAL);
+
+ bool drop = false;
+ Subnet4Ptr subnet = selectSubnet(request, drop);
+
+ // Stop here if selectSubnet decided to drop the packet
+ if (drop) {
+ return (Pkt4Ptr());
+ }
+
+ Dhcpv4Exchange ex(alloc_engine_, request, context, subnet, drop);
+
+ // Stop here if Dhcpv4Exchange constructor decided to drop the packet
+ if (drop) {
+ return (Pkt4Ptr());
+ }
+
+ // Note that we treat REQUEST message uniformly, regardless if this is a
+ // first request (requesting for new address), renewing existing address
+ // or even rebinding.
+ if (MultiThreadingMgr::instance().getMode()) {
+ // The lease reclamation cannot run at the same time.
+ ReadLockGuard share(alloc_engine_->getReadWriteMutex());
+
+ assignLease(ex);
+ } else {
+ assignLease(ex);
+ }
+
+ Pkt4Ptr response = ex.getResponse();
+ if (!response) {
+ // The ack is empty so return it *now*!
+ return (Pkt4Ptr());
+ } else if (request->inClass("BOOTP")) {
+ // Put BOOTP responses in the BOOTP class.
+ response->addClass("BOOTP");
+ }
+
+ // Adding any other options makes sense only when we got the lease.
+ if (!response->getYiaddr().isV4Zero()) {
+ // If this is global reservation or the subnet doesn't belong to a shared
+ // network we have already fetched it and evaluated the classes.
+ ex.conditionallySetReservedClientClasses();
+
+ // Required classification
+ requiredClassify(ex);
+
+ buildCfgOptionList(ex);
+ appendRequestedOptions(ex);
+ appendRequestedVendorOptions(ex);
+ // There are a few basic options that we always want to
+ // include in the response. If client did not request
+ // them we append them for him.
+ appendBasicOptions(ex);
+
+ // Set fixed fields (siaddr, sname, filename) if defined in
+ // the reservation, class or subnet specific configuration.
+ setFixedFields(ex);
+ }
+
+ // Set the src/dest IP address, port and interface for the outgoing
+ // packet.
+ adjustIfaceData(ex);
+
+ appendServerID(ex);
+
+ // Return the pointer to the context, which will be required by the
+ // leases4_committed callouts.
+ context = ex.getContext();
+
+ return (ex.getResponse());
+}
+
+void
+Dhcpv4Srv::processRelease(Pkt4Ptr& release, AllocEngine::ClientContext4Ptr& context) {
+ // Server-id is mandatory in DHCPRELEASE (see table 5, RFC2131)
+ // but ISC DHCP does not enforce this, so we'll follow suit.
+ sanityCheck(release, OPTIONAL);
+
+ // Try to find client-id. Note that for the DHCPRELEASE we don't check if the
+ // match-client-id configuration parameter is disabled because this parameter
+ // is configured for subnets and we don't select subnet for the DHCPRELEASE.
+ // Bogus clients usually generate new client identifiers when they first
+ // connect to the network, so whatever client identifier has been used to
+ // acquire the lease, the client identifier carried in the DHCPRELEASE is
+ // likely to be the same and the lease will be correctly identified in the
+ // lease database. If supplied client identifier differs from the one used
+ // to acquire the lease then the lease will remain in the database and
+ // simply expire.
+ ClientIdPtr client_id;
+ OptionPtr opt = release->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ if (opt) {
+ client_id = ClientIdPtr(new ClientId(opt->getData()));
+ }
+
+ try {
+ // Do we have a lease for that particular address?
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(release->getCiaddr());
+
+ if (!lease) {
+ // No such lease - bogus release
+ LOG_DEBUG(lease4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_NO_LEASE)
+ .arg(release->getLabel())
+ .arg(release->getCiaddr().toText());
+ return;
+ }
+
+ if (!lease->belongsToClient(release->getHWAddr(), client_id)) {
+ LOG_DEBUG(lease4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_WRONG_CLIENT)
+ .arg(release->getLabel())
+ .arg(release->getCiaddr().toText());
+ return;
+ }
+
+ bool skip = false;
+
+ // Execute all callouts registered for lease4_release
+ if (HooksManager::calloutsPresent(Hooks.hook_index_lease4_release_)) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(release);
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+ // Enable copying options from the packet within hook library.
+ ScopedEnableOptionsCopy<Pkt4> query4_options_copy(release);
+
+ // Pass the original packet
+ callout_handle->setArgument("query4", release);
+
+ // Pass the lease to be updated
+ callout_handle->setArgument("lease4", lease);
+
+ // Call all installed callouts
+ HooksManager::callCallouts(Hooks.hook_index_lease4_release_,
+ *callout_handle);
+
+ // Callouts decided to skip the next processing step. The next
+ // processing step would to send the packet, so skip at this
+ // stage means "drop response".
+ if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
+ (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
+ skip = true;
+ LOG_DEBUG(hooks_logger, DBGLVL_PKT_HANDLING,
+ DHCP4_HOOK_LEASE4_RELEASE_SKIP)
+ .arg(release->getLabel());
+ }
+ }
+
+ // Callout didn't indicate to skip the release process. Let's release
+ // the lease.
+ if (!skip) {
+ bool success = LeaseMgrFactory::instance().deleteLease(lease);
+
+ if (success) {
+
+ context.reset(new AllocEngine::ClientContext4());
+ context->old_lease_ = lease;
+
+ // Release successful
+ LOG_INFO(lease4_logger, DHCP4_RELEASE)
+ .arg(release->getLabel())
+ .arg(lease->addr_.toText());
+
+ // Need to decrease statistic for assigned addresses.
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", lease->subnet_id_, "assigned-addresses"),
+ static_cast<int64_t>(-1));
+
+ // Remove existing DNS entries for the lease, if any.
+ queueNCR(CHG_REMOVE, lease);
+
+ } else {
+ // Release failed
+ LOG_ERROR(lease4_logger, DHCP4_RELEASE_FAIL)
+ .arg(release->getLabel())
+ .arg(lease->addr_.toText());
+ }
+ }
+ } catch (const isc::Exception& ex) {
+ LOG_ERROR(lease4_logger, DHCP4_RELEASE_EXCEPTION)
+ .arg(release->getLabel())
+ .arg(release->getCiaddr())
+ .arg(ex.what());
+ }
+}
+
+void
+Dhcpv4Srv::processDecline(Pkt4Ptr& decline, AllocEngine::ClientContext4Ptr& context) {
+ // Server-id is mandatory in DHCPDECLINE (see table 5, RFC2131)
+ // but ISC DHCP does not enforce this, so we'll follow suit.
+ sanityCheck(decline, OPTIONAL);
+
+ // Client is supposed to specify the address being declined in
+ // Requested IP address option, but must not set its ciaddr.
+ // (again, see table 5 in RFC2131).
+
+ OptionCustomPtr opt_requested_address = boost::dynamic_pointer_cast<
+ OptionCustom>(decline->getOption(DHO_DHCP_REQUESTED_ADDRESS));
+ if (!opt_requested_address) {
+
+ isc_throw(RFCViolation, "Mandatory 'Requested IP address' option missing"
+ " in DHCPDECLINE sent from " << decline->getLabel());
+ }
+ IOAddress addr(opt_requested_address->readAddress());
+
+ // We could also extract client's address from ciaddr, but that's clearly
+ // against RFC2131.
+
+ // Now we need to check whether this address really belongs to the client
+ // that attempts to decline it.
+ const Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(addr);
+
+ if (!lease) {
+ // Client tried to decline an address, but we don't have a lease for
+ // that address. Let's ignore it.
+ //
+ // We could assume that we're recovering from a mishandled migration
+ // to a new server and mark the address as declined, but the window of
+ // opportunity for that to be useful is small and the attack vector
+ // would be pretty severe.
+ LOG_WARN(dhcp4_logger, DHCP4_DECLINE_LEASE_NOT_FOUND)
+ .arg(addr.toText()).arg(decline->getLabel());
+ return;
+ }
+
+ // Get client-id, if available.
+ OptionPtr opt_clientid = decline->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ ClientIdPtr client_id;
+ if (opt_clientid) {
+ client_id.reset(new ClientId(opt_clientid->getData()));
+ }
+
+ // Check if the client attempted to decline a lease it doesn't own.
+ if (!lease->belongsToClient(decline->getHWAddr(), client_id)) {
+
+ // Get printable hardware addresses
+ string client_hw = decline->getHWAddr() ?
+ decline->getHWAddr()->toText(false) : "(none)";
+ string lease_hw = lease->hwaddr_ ?
+ lease->hwaddr_->toText(false) : "(none)";
+
+ // Get printable client-ids
+ string client_id_txt = client_id ? client_id->toText() : "(none)";
+ string lease_id_txt = lease->client_id_ ?
+ lease->client_id_->toText() : "(none)";
+
+ // Print the warning and we're done here.
+ LOG_WARN(dhcp4_logger, DHCP4_DECLINE_LEASE_MISMATCH)
+ .arg(addr.toText()).arg(decline->getLabel())
+ .arg(client_hw).arg(lease_hw).arg(client_id_txt).arg(lease_id_txt);
+
+ return;
+ }
+
+ // Ok, all is good. The client is reporting its own address. Let's
+ // process it.
+ declineLease(lease, decline, context);
+}
+
+void
+Dhcpv4Srv::declineLease(const Lease4Ptr& lease, const Pkt4Ptr& decline,
+ AllocEngine::ClientContext4Ptr& context) {
+
+ // Let's check if there are hooks installed for decline4 hook point.
+ // If they are, let's pass the lease and client's packet. If the hook
+ // sets status to drop, we reject this Decline.
+ if (HooksManager::calloutsPresent(Hooks.hook_index_lease4_decline_)) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(decline);
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+ // Enable copying options from the packet within hook library.
+ ScopedEnableOptionsCopy<Pkt4> query4_options_copy(decline);
+
+ // Pass the original packet
+ callout_handle->setArgument("query4", decline);
+
+ // Pass the lease to be updated
+ callout_handle->setArgument("lease4", lease);
+
+ // Call callouts
+ HooksManager::callCallouts(Hooks.hook_index_lease4_decline_,
+ *callout_handle);
+
+ // Check if callouts decided to skip the next processing step.
+ // If any of them did, we will drop the packet.
+ if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
+ (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
+ LOG_DEBUG(hooks_logger, DBGLVL_PKT_HANDLING, DHCP4_HOOK_DECLINE_SKIP)
+ .arg(decline->getLabel()).arg(lease->addr_.toText());
+ return;
+ }
+ }
+
+ Lease4Ptr old_values = boost::make_shared<Lease4>(*lease);
+
+ // @todo: Call hooks.
+
+ // We need to disassociate the lease from the client. Once we move a lease
+ // to declined state, it is no longer associated with the client in any
+ // way.
+ lease->decline(CfgMgr::instance().getCurrentCfg()->getDeclinePeriod());
+
+ try {
+ LeaseMgrFactory::instance().updateLease4(lease);
+ } catch (const Exception& ex) {
+ // Update failed.
+ LOG_ERROR(lease4_logger, DHCP4_DECLINE_FAIL)
+ .arg(decline->getLabel())
+ .arg(lease->addr_.toText())
+ .arg(ex.what());
+ return;
+ }
+
+ // Remove existing DNS entries for the lease, if any.
+ // queueNCR will do the necessary checks and will skip the update, if not needed.
+ queueNCR(CHG_REMOVE, old_values);
+
+ // Bump up the statistics.
+
+ // Per subnet declined addresses counter.
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", lease->subnet_id_, "declined-addresses"),
+ static_cast<int64_t>(1));
+
+ // Global declined addresses counter.
+ StatsMgr::instance().addValue("declined-addresses", static_cast<int64_t>(1));
+
+ // We do not want to decrease the assigned-addresses at this time. While
+ // technically a declined address is no longer allocated, the primary usage
+ // of the assigned-addresses statistic is to monitor pool utilization. Most
+ // people would forget to include declined-addresses in the calculation,
+ // and simply do assigned-addresses/total-addresses. This would have a bias
+ // towards under-representing pool utilization, if we decreased allocated
+ // immediately after receiving DHCPDECLINE, rather than later when we recover
+ // the address.
+
+ context.reset(new AllocEngine::ClientContext4());
+ context->new_lease_ = lease;
+
+ LOG_INFO(lease4_logger, DHCP4_DECLINE_LEASE).arg(lease->addr_.toText())
+ .arg(decline->getLabel()).arg(lease->valid_lft_);
+}
+
+Pkt4Ptr
+Dhcpv4Srv::processInform(Pkt4Ptr& inform, AllocEngine::ClientContext4Ptr& context) {
+ // server-id is supposed to be forbidden (as is requested address)
+ // but ISC DHCP does not enforce either. So neither will we.
+ sanityCheck(inform, OPTIONAL);
+
+ bool drop = false;
+ Subnet4Ptr subnet = selectSubnet(inform, drop);
+
+ // Stop here if selectSubnet decided to drop the packet
+ if (drop) {
+ return (Pkt4Ptr());
+ }
+
+ Dhcpv4Exchange ex(alloc_engine_, inform, context, subnet, drop);
+
+ // Stop here if Dhcpv4Exchange constructor decided to drop the packet
+ if (drop) {
+ return (Pkt4Ptr());
+ }
+
+ Pkt4Ptr ack = ex.getResponse();
+
+ // If this is global reservation or the subnet doesn't belong to a shared
+ // network we have already fetched it and evaluated the classes.
+ ex.conditionallySetReservedClientClasses();
+
+ requiredClassify(ex);
+
+ buildCfgOptionList(ex);
+ appendRequestedOptions(ex);
+ appendRequestedVendorOptions(ex);
+ appendBasicOptions(ex);
+ adjustIfaceData(ex);
+
+ // Set fixed fields (siaddr, sname, filename) if defined in
+ // the reservation, class or subnet specific configuration.
+ setFixedFields(ex);
+
+ // There are cases for the DHCPINFORM that the server receives it via
+ // relay but will send the response to the client's unicast address
+ // carried in the ciaddr. In this case, the giaddr and hops field should
+ // be cleared (these fields were copied by the copyDefaultFields function).
+ // Also Relay Agent Options should be removed if present.
+ if (ack->getRemoteAddr() != inform->getGiaddr()) {
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL, DHCP4_INFORM_DIRECT_REPLY)
+ .arg(inform->getLabel())
+ .arg(ack->getRemoteAddr())
+ .arg(ack->getIface());
+ ack->setHops(0);
+ ack->setGiaddr(IOAddress::IPV4_ZERO_ADDRESS());
+ ack->delOption(DHO_DHCP_AGENT_OPTIONS);
+ }
+
+ // The DHCPACK must contain server id.
+ appendServerID(ex);
+
+ return (ex.getResponse());
+}
+
+bool
+Dhcpv4Srv::accept(const Pkt4Ptr& query) const {
+ // Check that the message type is accepted by the server. We rely on the
+ // function called to log a message if needed.
+ if (!acceptMessageType(query)) {
+ return (false);
+ }
+ // Check if the message from directly connected client (if directly
+ // connected) should be dropped or processed.
+ if (!acceptDirectRequest(query)) {
+ LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0002)
+ .arg(query->getLabel())
+ .arg(query->getIface());
+ return (false);
+ }
+
+ // Check if the DHCPv4 packet has been sent to us or to someone else.
+ // If it hasn't been sent to us, drop it!
+ if (!acceptServerId(query)) {
+ LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0003)
+ .arg(query->getLabel())
+ .arg(query->getIface());
+ return (false);
+ }
+
+ return (true);
+}
+
+bool
+Dhcpv4Srv::acceptDirectRequest(const Pkt4Ptr& pkt) const {
+ // Accept all relayed messages.
+ if (pkt->isRelayed()) {
+ return (true);
+ }
+
+ // Accept all DHCPv4-over-DHCPv6 messages.
+ if (pkt->isDhcp4o6()) {
+ return (true);
+ }
+
+ // The source address must not be zero for the DHCPINFORM message from
+ // the directly connected client because the server will not know where
+ // to respond if the ciaddr was not present.
+ try {
+ if (pkt->getType() == DHCPINFORM) {
+ if (pkt->getRemoteAddr().isV4Zero() &&
+ pkt->getCiaddr().isV4Zero()) {
+ return (false);
+ }
+ }
+ } catch (...) {
+ // If we got here, it is probably because the message type hasn't
+ // been set. But, this should not really happen assuming that
+ // we validate the message type prior to calling this function.
+ return (false);
+ }
+ bool drop = false;
+ bool result = (!pkt->getLocalAddr().isV4Bcast() ||
+ selectSubnet(pkt, drop, true));
+ if (drop) {
+ // The packet must be dropped but as sanity_only is true it is dead code.
+ return (false);
+ }
+ return (result);
+}
+
+bool
+Dhcpv4Srv::acceptMessageType(const Pkt4Ptr& query) const {
+ // When receiving a packet without message type option, getType() will
+ // throw.
+ int type;
+ try {
+ type = query->getType();
+
+ } catch (...) {
+ LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0004)
+ .arg(query->getLabel())
+ .arg(query->getIface());
+ return (false);
+ }
+
+ // Once we know that the message type is within a range of defined DHCPv4
+ // messages, we do a detailed check to make sure that the received message
+ // is targeted at server. Note that we could have received some Offer
+ // message broadcasted by the other server to a relay. Even though, the
+ // server would rather unicast its response to a relay, let's be on the
+ // safe side. Also, we want to drop other messages which we don't support.
+ // All these valid messages that we are not going to process are dropped
+ // silently.
+
+ switch(type) {
+ case DHCPDISCOVER:
+ case DHCPREQUEST:
+ case DHCPRELEASE:
+ case DHCPDECLINE:
+ case DHCPINFORM:
+ return (true);
+ break;
+
+ case DHCP_NOTYPE:
+ LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0009)
+ .arg(query->getLabel());
+ break;
+
+ default:
+ // If we receive a message with a non-existing type, we are logging it.
+ if (type >= DHCP_TYPES_EOF) {
+ LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0005)
+ .arg(query->getLabel())
+ .arg(type);
+ } else {
+ // Exists but we don't support it.
+ LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0006)
+ .arg(query->getLabel())
+ .arg(type);
+ }
+ break;
+ }
+
+ return (false);
+}
+
+bool
+Dhcpv4Srv::acceptServerId(const Pkt4Ptr& query) const {
+ // This function is meant to be called internally by the server class, so
+ // we rely on the caller to sanity check the pointer and we don't check
+ // it here.
+
+ // Check if server identifier option is present. If it is not present
+ // we accept the message because it is targeted to all servers.
+ // Note that we don't check cases that server identifier is mandatory
+ // but not present. This is meant to be sanity checked in other
+ // functions.
+ OptionPtr option = query->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+ if (!option) {
+ return (true);
+ }
+ // Server identifier is present. Let's convert it to 4-byte address
+ // and try to match with server identifiers used by the server.
+ OptionCustomPtr option_custom =
+ boost::dynamic_pointer_cast<OptionCustom>(option);
+ // Unable to convert the option to the option type which encapsulates it.
+ // We treat this as non-matching server id.
+ if (!option_custom) {
+ return (false);
+ }
+ // The server identifier option should carry exactly one IPv4 address.
+ // If the option definition for the server identifier doesn't change,
+ // the OptionCustom object should have exactly one IPv4 address and
+ // this check is somewhat redundant. On the other hand, if someone
+ // breaks option it may be better to check that here.
+ if (option_custom->getDataFieldsNum() != 1) {
+ return (false);
+ }
+
+ // The server identifier MUST be an IPv4 address. If given address is
+ // v6, it is wrong.
+ IOAddress server_id = option_custom->readAddress();
+ if (!server_id.isV4()) {
+ return (false);
+ }
+
+ // According to RFC5107, the RAI_OPTION_SERVER_ID_OVERRIDE option if
+ // present, should match DHO_DHCP_SERVER_IDENTIFIER option.
+ OptionPtr rai_option = query->getOption(DHO_DHCP_AGENT_OPTIONS);
+ if (rai_option) {
+ OptionPtr rai_suboption = rai_option->getOption(RAI_OPTION_SERVER_ID_OVERRIDE);
+ if (rai_suboption && (server_id.toBytes() == rai_suboption->toBinary())) {
+ return (true);
+ }
+ }
+
+ // This function iterates over all interfaces on which the
+ // server is listening to find the one which has a socket bound
+ // to the address carried in the server identifier option.
+ // This has some performance implications. However, given that
+ // typically there will be just a few active interfaces the
+ // performance hit should be acceptable. If it turns out to
+ // be significant, we will have to cache server identifiers
+ // when sockets are opened.
+ if (IfaceMgr::instance().hasOpenSocket(server_id)) {
+ return (true);
+ }
+
+ // There are some cases when an administrator explicitly sets server
+ // identifier (option 54) that should be used for a given, subnet,
+ // network etc. It doesn't have to be an address assigned to any of
+ // the server interfaces. Thus, we have to check if the server
+ // identifier received is the one that we explicitly set in the
+ // server configuration. At this point, we don't know which subnet
+ // the client belongs to so we can't match the server id with any
+ // subnet. We simply check if this server identifier is configured
+ // anywhere. This should be good enough to eliminate exchanges
+ // with other servers in the same network.
+
+ /// @todo Currently we only check server identifiers configured at the
+ /// subnet, shared network, client class and global levels.
+ /// This should be sufficient for most of cases. At this point, trying to
+ /// support server identifiers on the host reservations level seems to be an
+ /// overkill and is probably not needed. In fact, at this point we don't
+ /// know the reservations for the client communicating with the server.
+ /// We may revise some of these choices in the future.
+
+ SrvConfigPtr cfg = CfgMgr::instance().getCurrentCfg();
+
+ // Check if there is at least one subnet configured with this server
+ // identifier.
+ ConstCfgSubnets4Ptr cfg_subnets = cfg->getCfgSubnets4();
+ if (cfg_subnets->hasSubnetWithServerId(server_id)) {
+ return (true);
+ }
+
+ // This server identifier is not configured for any of the subnets, so
+ // check on the shared network level.
+ CfgSharedNetworks4Ptr cfg_networks = cfg->getCfgSharedNetworks4();
+ if (cfg_networks->hasNetworkWithServerId(server_id)) {
+ return (true);
+ }
+
+ // Check if the server identifier is configured at client class level.
+ const ClientClasses& classes = query->getClasses();
+ for (ClientClasses::const_iterator cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ // Find the client class definition for this class
+ const ClientClassDefPtr& ccdef = CfgMgr::instance().getCurrentCfg()->
+ getClientClassDictionary()->findClass(*cclass);
+ if (!ccdef) {
+ continue;
+ }
+
+ if (ccdef->getCfgOption()->empty()) {
+ // Skip classes which don't configure options
+ continue;
+ }
+
+ OptionCustomPtr context_opt_server_id = boost::dynamic_pointer_cast<OptionCustom>
+ (ccdef->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_DHCP_SERVER_IDENTIFIER).option_);
+ if (context_opt_server_id && (context_opt_server_id->readAddress() == server_id)) {
+ return (true);
+ }
+ }
+
+ // Finally, it is possible that the server identifier is specified
+ // on the global level.
+ ConstCfgOptionPtr cfg_global_options = cfg->getCfgOption();
+ OptionCustomPtr opt_server_id = boost::dynamic_pointer_cast<OptionCustom>
+ (cfg_global_options->get(DHCP4_OPTION_SPACE, DHO_DHCP_SERVER_IDENTIFIER).option_);
+
+ return (opt_server_id && (opt_server_id->readAddress() == server_id));
+}
+
+void
+Dhcpv4Srv::sanityCheck(const Pkt4Ptr& query, RequirementLevel serverid) {
+ OptionPtr server_id = query->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+ switch (serverid) {
+ case FORBIDDEN:
+ if (server_id) {
+ isc_throw(RFCViolation, "Server-id option was not expected, but"
+ << " received in message "
+ << query->getName());
+ }
+ break;
+
+ case MANDATORY:
+ if (!server_id) {
+ isc_throw(RFCViolation, "Server-id option was expected, but not"
+ " received in message "
+ << query->getName());
+ }
+ break;
+
+ case OPTIONAL:
+ // do nothing here
+ ;
+ }
+
+ // If there is HWAddress set and it is non-empty, then we're good
+ if (query->getHWAddr() && !query->getHWAddr()->hwaddr_.empty()) {
+ return;
+ }
+
+ // There has to be something to uniquely identify the client:
+ // either non-zero MAC address or client-id option present (or both)
+ OptionPtr client_id = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+
+ // If there's no client-id (or a useless one is provided, i.e. 0 length)
+ if (!client_id || client_id->len() == client_id->getHeaderLen()) {
+ isc_throw(RFCViolation, "Missing or useless client-id and no HW address"
+ " provided in message "
+ << query->getName());
+ }
+}
+
+void Dhcpv4Srv::classifyPacket(const Pkt4Ptr& pkt) {
+ Dhcpv4Exchange::classifyPacket(pkt);
+}
+
+void Dhcpv4Srv::requiredClassify(Dhcpv4Exchange& ex) {
+ // First collect required classes
+ Pkt4Ptr query = ex.getQuery();
+ ClientClasses classes = query->getClasses(true);
+ Subnet4Ptr subnet = ex.getContext()->subnet_;
+
+ if (subnet) {
+ // Begin by the shared-network
+ SharedNetwork4Ptr network;
+ subnet->getSharedNetwork(network);
+ if (network) {
+ const ClientClasses& to_add = network->getRequiredClasses();
+ for (ClientClasses::const_iterator cclass = to_add.cbegin();
+ cclass != to_add.cend(); ++cclass) {
+ classes.insert(*cclass);
+ }
+ }
+
+ // Followed by the subnet
+ const ClientClasses& to_add = subnet->getRequiredClasses();
+ for(ClientClasses::const_iterator cclass = to_add.cbegin();
+ cclass != to_add.cend(); ++cclass) {
+ classes.insert(*cclass);
+ }
+
+ // And finish by the pool
+ Pkt4Ptr resp = ex.getResponse();
+ IOAddress addr = IOAddress::IPV4_ZERO_ADDRESS();
+ if (resp) {
+ addr = resp->getYiaddr();
+ }
+ if (!addr.isV4Zero()) {
+ PoolPtr pool = subnet->getPool(Lease::TYPE_V4, addr, false);
+ if (pool) {
+ const ClientClasses& to_add = pool->getRequiredClasses();
+ for (ClientClasses::const_iterator cclass = to_add.cbegin();
+ cclass != to_add.cend(); ++cclass) {
+ classes.insert(*cclass);
+ }
+ }
+ }
+
+ // host reservation???
+ }
+
+ // Run match expressions
+ // Note getClientClassDictionary() cannot be null
+ const ClientClassDictionaryPtr& dict =
+ CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
+ for (ClientClasses::const_iterator cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ const ClientClassDefPtr class_def = dict->findClass(*cclass);
+ if (!class_def) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_UNDEFINED)
+ .arg(*cclass);
+ continue;
+ }
+ const ExpressionPtr& expr_ptr = class_def->getMatchExpr();
+ // Nothing to do without an expression to evaluate
+ if (!expr_ptr) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_UNTESTABLE)
+ .arg(*cclass);
+ continue;
+ }
+ // Evaluate the expression which can return false (no match),
+ // true (match) or raise an exception (error)
+ try {
+ bool status = evaluateBool(*expr_ptr, *query);
+ if (status) {
+ LOG_INFO(options4_logger, EVAL_RESULT)
+ .arg(*cclass)
+ .arg(status);
+ // Matching: add the class
+ query->addClass(*cclass);
+ } else {
+ LOG_DEBUG(options4_logger, DBG_DHCP4_DETAIL, EVAL_RESULT)
+ .arg(*cclass)
+ .arg(status);
+ }
+ } catch (const Exception& ex) {
+ LOG_ERROR(options4_logger, EVAL_RESULT)
+ .arg(*cclass)
+ .arg(ex.what());
+ } catch (...) {
+ LOG_ERROR(options4_logger, EVAL_RESULT)
+ .arg(*cclass)
+ .arg("get exception?");
+ }
+ }
+}
+
+void
+Dhcpv4Srv::deferredUnpack(Pkt4Ptr& query) {
+ // Iterate on the list of deferred option codes
+ BOOST_FOREACH(const uint16_t& code, query->getDeferredOptions()) {
+ OptionDefinitionPtr def;
+ // Iterate on client classes
+ const ClientClasses& classes = query->getClasses();
+ for (ClientClasses::const_iterator cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ // Get the client class definition for this class
+ const ClientClassDefPtr& ccdef =
+ CfgMgr::instance().getCurrentCfg()->
+ getClientClassDictionary()->findClass(*cclass);
+ // If not found skip it
+ if (!ccdef) {
+ continue;
+ }
+ // If there is no option definition skip it
+ if (!ccdef->getCfgOptionDef()) {
+ continue;
+ }
+ def = ccdef->getCfgOptionDef()->get(DHCP4_OPTION_SPACE, code);
+ // Stop at the first client class with a definition
+ if (def) {
+ break;
+ }
+ }
+ // If not found try the global definition
+ if (!def) {
+ def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, code);
+ }
+ if (!def) {
+ def = LibDHCP::getRuntimeOptionDef(DHCP4_OPTION_SPACE, code);
+ }
+ // Finish by last resort definition
+ if (!def) {
+ def = LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE, code);
+ }
+ // If not defined go to the next option
+ if (!def) {
+ continue;
+ }
+ // Get the existing option for its content and remove all
+ OptionPtr opt = query->getOption(code);
+ if (!opt) {
+ // should not happen but do not crash anyway
+ LOG_DEBUG(bad_packet4_logger, DBG_DHCP4_DETAIL,
+ DHCP4_DEFERRED_OPTION_MISSING)
+ .arg(code);
+ continue;
+ }
+ // Because options have already been fused, the buffer contains entire
+ // data.
+ const OptionBuffer buf = opt->getData();
+ try {
+ // Unpack the option
+ opt = def->optionFactory(Option::V4, code, buf);
+ } catch (const std::exception& e) {
+ // Failed to parse the option.
+ LOG_DEBUG(bad_packet4_logger, DBG_DHCP4_DETAIL,
+ DHCP4_DEFERRED_OPTION_UNPACK_FAIL)
+ .arg(code)
+ .arg(e.what());
+ continue;
+ }
+ while (query->delOption(code)) {
+ // continue
+ }
+ // Add the unpacked option.
+ query->addOption(opt);
+ }
+}
+
+void
+Dhcpv4Srv::startD2() {
+ D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
+ if (d2_mgr.ddnsEnabled()) {
+ // Updates are enabled, so lets start the sender, passing in
+ // our error handler.
+ // This may throw so wherever this is called needs to ready.
+ d2_mgr.startSender(std::bind(&Dhcpv4Srv::d2ClientErrorHandler,
+ this, ph::_1, ph::_2));
+ }
+}
+
+void
+Dhcpv4Srv::stopD2() {
+ D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
+ if (d2_mgr.ddnsEnabled()) {
+ // Updates are enabled, so lets stop the sender
+ d2_mgr.stopSender();
+ }
+}
+
+void
+Dhcpv4Srv::d2ClientErrorHandler(const
+ dhcp_ddns::NameChangeSender::Result result,
+ dhcp_ddns::NameChangeRequestPtr& ncr) {
+ LOG_ERROR(ddns4_logger, DHCP4_DDNS_REQUEST_SEND_FAILED).
+ arg(result).arg((ncr ? ncr->toText() : " NULL "));
+ // We cannot communicate with kea-dhcp-ddns, suspend further updates.
+ /// @todo We may wish to revisit this, but for now we will simply turn
+ /// them off.
+ CfgMgr::instance().getD2ClientMgr().suspendUpdates();
+}
+
+// Refer to config_report so it will be embedded in the binary
+const char* const* dhcp4_config_report = isc::detail::config_report;
+
+std::string
+Dhcpv4Srv::getVersion(bool extended) {
+ std::stringstream tmp;
+
+ tmp << VERSION;
+ if (extended) {
+ tmp << endl << EXTENDED_VERSION << endl;
+ tmp << "linked with:" << endl;
+ tmp << Logger::getVersion() << endl;
+ tmp << CryptoLink::getVersion() << endl;
+ tmp << "database:" << endl;
+#ifdef HAVE_MYSQL
+ tmp << MySqlLeaseMgr::getDBVersion() << endl;
+#endif
+#ifdef HAVE_PGSQL
+ tmp << PgSqlLeaseMgr::getDBVersion() << endl;
+#endif
+ tmp << Memfile_LeaseMgr::getDBVersion(Memfile_LeaseMgr::V4);
+
+ // @todo: more details about database runtime
+ }
+
+ return (tmp.str());
+}
+
+void Dhcpv4Srv::processStatsReceived(const Pkt4Ptr& query) {
+ // Note that we're not bumping pkt4-received statistic as it was
+ // increased early in the packet reception code.
+
+ string stat_name = "pkt4-unknown-received";
+ try {
+ switch (query->getType()) {
+ case DHCPDISCOVER:
+ stat_name = "pkt4-discover-received";
+ break;
+ case DHCPOFFER:
+ // Should not happen, but let's keep a counter for it
+ stat_name = "pkt4-offer-received";
+ break;
+ case DHCPREQUEST:
+ stat_name = "pkt4-request-received";
+ break;
+ case DHCPACK:
+ // Should not happen, but let's keep a counter for it
+ stat_name = "pkt4-ack-received";
+ break;
+ case DHCPNAK:
+ // Should not happen, but let's keep a counter for it
+ stat_name = "pkt4-nak-received";
+ break;
+ case DHCPRELEASE:
+ stat_name = "pkt4-release-received";
+ break;
+ case DHCPDECLINE:
+ stat_name = "pkt4-decline-received";
+ break;
+ case DHCPINFORM:
+ stat_name = "pkt4-inform-received";
+ break;
+ default:
+ ; // do nothing
+ }
+ }
+ catch (...) {
+ // If the incoming packet doesn't have option 53 (message type)
+ // or a hook set pkt4_receive_skip, then Pkt4::getType() may
+ // throw an exception. That's ok, we'll then use the default
+ // name of pkt4-unknown-received.
+ }
+
+ isc::stats::StatsMgr::instance().addValue(stat_name,
+ static_cast<int64_t>(1));
+}
+
+void Dhcpv4Srv::processStatsSent(const Pkt4Ptr& response) {
+ // Increase generic counter for sent packets.
+ isc::stats::StatsMgr::instance().addValue("pkt4-sent",
+ static_cast<int64_t>(1));
+
+ // Increase packet type specific counter for packets sent.
+ string stat_name;
+ switch (response->getType()) {
+ case DHCPOFFER:
+ stat_name = "pkt4-offer-sent";
+ break;
+ case DHCPACK:
+ stat_name = "pkt4-ack-sent";
+ break;
+ case DHCPNAK:
+ stat_name = "pkt4-nak-sent";
+ break;
+ default:
+ // That should never happen
+ return;
+ }
+
+ isc::stats::StatsMgr::instance().addValue(stat_name,
+ static_cast<int64_t>(1));
+}
+
+int Dhcpv4Srv::getHookIndexBuffer4Receive() {
+ return (Hooks.hook_index_buffer4_receive_);
+}
+
+int Dhcpv4Srv::getHookIndexPkt4Receive() {
+ return (Hooks.hook_index_pkt4_receive_);
+}
+
+int Dhcpv4Srv::getHookIndexSubnet4Select() {
+ return (Hooks.hook_index_subnet4_select_);
+}
+
+int Dhcpv4Srv::getHookIndexLease4Release() {
+ return (Hooks.hook_index_lease4_release_);
+}
+
+int Dhcpv4Srv::getHookIndexPkt4Send() {
+ return (Hooks.hook_index_pkt4_send_);
+}
+
+int Dhcpv4Srv::getHookIndexBuffer4Send() {
+ return (Hooks.hook_index_buffer4_send_);
+}
+
+int Dhcpv4Srv::getHookIndexLease4Decline() {
+ return (Hooks.hook_index_lease4_decline_);
+}
+
+void Dhcpv4Srv::discardPackets() {
+ // Dump all of our current packets, anything that is mid-stream
+ HooksManager::clearParkingLots();
+}
+
+std::list<std::list<std::string>> Dhcpv4Srv::jsonPathsToRedact() const {
+ static std::list<std::list<std::string>> const list({
+ {"config-control", "config-databases", "[]"},
+ {"hooks-libraries", "[]", "parameters", "*"},
+ {"hosts-database"},
+ {"hosts-databases", "[]"},
+ {"lease-database"},
+ });
+ return list;
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h
new file mode 100644
index 0000000..ba7152b
--- /dev/null
+++ b/src/bin/dhcp4/dhcp4_srv.h
@@ -0,0 +1,1200 @@
+// Copyright (C) 2011-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/.
+
+#ifndef DHCPV4_SRV_H
+#define DHCPV4_SRV_H
+
+#include <asiolink/io_service.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/option.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option4_client_fqdn.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/pkt4.h>
+#include <dhcp_ddns/ncr_msg.h>
+#include <dhcpsrv/alloc_engine.h>
+#include <dhcpsrv/callout_handle_store.h>
+#include <dhcpsrv/cb_ctl_dhcp4.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/d2_client_mgr.h>
+#include <dhcpsrv/network_state.h>
+#include <dhcpsrv/subnet.h>
+#include <hooks/callout_handle.h>
+#include <process/daemon.h>
+
+#include <functional>
+#include <iostream>
+#include <queue>
+
+// Undefine the macro OPTIONAL which is defined in some operating
+// systems but conflicts with a member of the RequirementLevel enum in
+// the server class.
+
+#ifdef OPTIONAL
+#undef OPTIONAL
+#endif
+
+namespace isc {
+namespace dhcp {
+
+/// @brief DHCPv4 message exchange.
+///
+/// This class represents the DHCPv4 message exchange. The message exchange
+/// consists of the single client message, server response to this message
+/// and the mechanisms to generate the server's response. The server creates
+/// the instance of the @c Dhcpv4Exchange for each inbound message that it
+/// accepts for processing.
+///
+/// The use of the @c Dhcpv4Exchange object as a central repository of
+/// information about the message exchange simplifies the API of the
+/// @c Dhcpv4Srv class.
+///
+/// Another benefit of using this class is that different methods of the
+/// @c Dhcpv4Srv may share information. For example, the constructor of this
+/// class selects the subnet and multiple methods of @c Dhcpv4Srv use this
+/// subnet, without the need to select it again.
+///
+/// @todo This is the initial version of this class. In the future a lot of
+/// code from the @c Dhcpv4Srv class will be migrated here.
+class Dhcpv4Exchange {
+public:
+ /// @brief Constructor.
+ ///
+ /// The constructor selects the subnet for the query and checks for the
+ /// static host reservations for the client which has sent the message.
+ /// The information about the reservations is stored in the
+ /// @c AllocEngine::ClientContext4 object, which can be obtained by
+ /// calling the @c getContext.
+ ///
+ /// @param alloc_engine Pointer to the instance of the Allocation Engine
+ /// used by the server.
+ /// @param query Pointer to the client message.
+ /// @param context Pointer to the client context.
+ /// @param subnet Pointer to the subnet to which the client belongs.
+ /// @param drop if it is true the packet will be dropped.
+ Dhcpv4Exchange(const AllocEnginePtr& alloc_engine, const Pkt4Ptr& query,
+ AllocEngine::ClientContext4Ptr& context,
+ const Subnet4Ptr& subnet, bool& drop);
+
+ /// @brief Initializes the instance of the response message.
+ ///
+ /// The type of the response depends on the type of the query message.
+ /// For the DHCPDISCOVER the DHCPOFFER is created. For the DHCPREQUEST
+ /// and DHCPINFORM the DHCPACK is created. For the DHCPRELEASE the
+ /// response is not initialized.
+ void initResponse();
+
+ /// @brief Initializes the DHCPv6 part of the response message
+ ///
+ /// Called by initResponse() when the query is a DHCP4o6 message
+ void initResponse4o6();
+
+ /// @brief Returns the pointer to the query from the client.
+ Pkt4Ptr getQuery() const {
+ return (query_);
+ }
+
+ /// @brief Returns the pointer to the server's response.
+ ///
+ /// The returned pointer is NULL if the query type is DHCPRELEASE or DHCPDECLINE.
+ Pkt4Ptr getResponse() const {
+ return (resp_);
+ }
+
+ /// @brief Removes the response message by resetting the pointer to NULL.
+ void deleteResponse() {
+ resp_.reset();
+ }
+
+ /// @brief Returns the copy of the context for the Allocation engine.
+ AllocEngine::ClientContext4Ptr getContext() const {
+ return (context_);
+ }
+
+ /// @brief Returns the configured option list (non-const version)
+ CfgOptionList& getCfgOptionList() {
+ return (cfg_option_list_);
+ }
+
+ /// @brief Returns the configured option list (const version)
+ const CfgOptionList& getCfgOptionList() const {
+ return (cfg_option_list_);
+ }
+
+ /// @brief Sets reserved values of siaddr, sname and file in the
+ /// server's response.
+ void setReservedMessageFields();
+
+ /// @brief Set host identifiers within a context.
+ ///
+ /// This method sets an ordered list of host identifier types and
+ /// values which the server should use to find host reservations.
+ /// The order of the set is determined by the configuration parameter,
+ /// host-reservation-identifiers
+ ///
+ /// @param context pointer to the context.
+ static void setHostIdentifiers(AllocEngine::ClientContext4Ptr context);
+
+ /// @brief Removed evaluated client classes.
+ ///
+ /// @todo: keep the list of dependent evaluated classes so
+ /// remove only them.
+ ///
+ /// @param query the query message.
+ static void removeDependentEvaluatedClasses(const Pkt4Ptr& query);
+
+ /// @brief Assigns classes retrieved from host reservation database.
+ ///
+ /// @param context pointer to the context.
+ static void setReservedClientClasses(AllocEngine::ClientContext4Ptr context);
+
+ /// @brief Assigns classes retrieved from host reservation database
+ /// if they haven't been yet set.
+ ///
+ /// This function sets reserved client classes in case they haven't
+ /// been set after fetching host reservations from the database.
+ /// This is the case when the client has non-global host reservation
+ /// and the selected subnet belongs to a shared network.
+ void conditionallySetReservedClientClasses();
+
+ /// @brief Assigns incoming packet to zero or more classes.
+ ///
+ /// @note This is done in two phases: first the content of the
+ /// vendor-class-identifier option is used as a class, by
+ /// calling @ref classifyByVendor(). Second, the classification match
+ /// expressions are evaluated. The resulting classes will be stored
+ /// in the packet (see @ref isc::dhcp::Pkt4::classes_ and
+ /// @ref isc::dhcp::Pkt4::inClass).
+ ///
+ /// @param pkt packet to be classified
+ static void classifyPacket(const Pkt4Ptr& pkt);
+
+ /// @brief Evaluate classes.
+ ///
+ /// @note Second part of the classification.
+ ///
+ /// Evaluate expressions of client classes: if it returns true the class
+ /// is added to the incoming packet.
+ ///
+ /// @param pkt packet to be classified.
+ /// @param depend_on_known if false classes depending on the KNOWN or
+ /// UNKNOWN classes are skipped, if true only these classes are evaluated.
+ static void evaluateClasses(const Pkt4Ptr& pkt, bool depend_on_known);
+
+private:
+
+ /// @public
+ /// @brief Assign class using vendor-class-identifier option
+ ///
+ /// @note This is the first part of @ref classifyPacket
+ ///
+ /// @param pkt packet to be classified
+ static void classifyByVendor(const Pkt4Ptr& pkt);
+
+ /// @brief Copies default parameters from client's to server's message
+ ///
+ /// Some fields are copied from client's message into server's response,
+ /// e.g. client HW address, number of hops, transaction-id etc.
+ ///
+ /// @warning This message is called internally by @c initResponse and
+ /// thus it doesn't check if the resp_ value has been initialized. The
+ /// calling method is responsible for making sure that @c resp_ is
+ /// not NULL.
+ void copyDefaultFields();
+
+ /// @brief Copies default options from client's to server's message
+ ///
+ /// Some options are copied from client's message into server's response,
+ /// e.g. Relay Agent Info option, Subnet Selection option etc.
+ ///
+ /// @warning This message is called internally by @c initResponse and
+ /// thus it doesn't check if the resp_ value has been initialized. The
+ /// calling method is responsible for making sure that @c resp_ is
+ /// not NULL.
+ void copyDefaultOptions();
+
+ /// @brief Pointer to the allocation engine used by the server.
+ AllocEnginePtr alloc_engine_;
+
+ /// @brief Pointer to the DHCPv4 message sent by the client.
+ Pkt4Ptr query_;
+
+ /// @brief Pointer to the DHCPv4 message to be sent to the client.
+ Pkt4Ptr resp_;
+
+ /// @brief Context for use with allocation engine.
+ AllocEngine::ClientContext4Ptr context_;
+
+ /// @brief Configured option list.
+ /// @note The configured option list is an *ordered* list of
+ /// @c CfgOption objects used to append options to the response.
+ CfgOptionList cfg_option_list_;
+};
+
+/// @brief Type representing the pointer to the @c Dhcpv4Exchange.
+typedef boost::shared_ptr<Dhcpv4Exchange> Dhcpv4ExchangePtr;
+
+
+/// @brief DHCPv4 server service.
+///
+/// This singleton class represents DHCPv4 server. It contains all
+/// top-level methods and routines necessary for server operation.
+/// In particular, it instantiates IfaceMgr, loads or generates DUID
+/// that is going to be used as server-identifier, receives incoming
+/// packets, processes them, manages leases assignment and generates
+/// appropriate responses.
+///
+/// This class does not support any controlling mechanisms directly.
+/// See the derived \ref ControlledDhcpv4Srv class for support for
+/// command and configuration updates over msgq.
+class Dhcpv4Srv : public process::Daemon {
+private:
+
+ /// @brief Pointer to IO service used by the server.
+ asiolink::IOServicePtr io_service_;
+
+public:
+
+ /// @brief defines if certain option may, must or must not appear
+ typedef enum {
+ FORBIDDEN,
+ MANDATORY,
+ OPTIONAL
+ } RequirementLevel;
+
+ /// @brief Default constructor.
+ ///
+ /// Instantiates necessary services, required to run DHCPv4 server.
+ /// In particular, creates IfaceMgr that will be responsible for
+ /// network interaction. Will instantiate lease manager, and load
+ /// old or create new DUID. It is possible to specify alternate
+ /// port on which DHCPv4 server will listen on and alternate port
+ /// where DHCPv4 server sends all responses to. Those are mostly useful
+ /// for testing purposes. The Last two arguments of the constructor
+ /// should be left at default values for normal server operation.
+ /// They should be set to 'false' when creating an instance of this
+ /// class for unit testing because features they enable require
+ /// root privileges.
+ ///
+ /// @param server_port specifies port number to listen on
+ /// @param client_port specifies port number to send to
+ /// @param use_bcast configure sockets to support broadcast messages.
+ /// @param direct_response_desired specifies if it is desired to
+ /// use direct V4 traffic.
+ Dhcpv4Srv(uint16_t server_port = DHCP4_SERVER_PORT,
+ uint16_t client_port = 0,
+ const bool use_bcast = true,
+ const bool direct_response_desired = true);
+
+ /// @brief Destructor. Used during DHCPv4 service shutdown.
+ virtual ~Dhcpv4Srv();
+
+ /// @brief Checks if the server is running in unit test mode.
+ ///
+ /// @return true if the server is running in unit test mode,
+ /// false otherwise.
+ bool inTestMode() const {
+ return (server_port_ == 0);
+ }
+
+ /// @brief Returns pointer to the IO service used by the server.
+ asiolink::IOServicePtr& getIOService() {
+ return (io_service_);
+ }
+
+ /// @brief Returns pointer to the network state used by the server.
+ NetworkStatePtr& getNetworkState() {
+ return (network_state_);
+ }
+
+ /// @brief Returns an object which controls access to the configuration
+ /// backends.
+ ///
+ /// @return Pointer to the instance of the object which controls
+ /// access to the configuration backends.
+ CBControlDHCPv4Ptr getCBControl() const {
+ return (cb_control_);
+ }
+
+ /// @brief returns Kea version on stdout and exit.
+ /// redeclaration/redefinition. @ref isc::process::Daemon::getVersion()
+ static std::string getVersion(bool extended);
+
+ /// @brief Main server processing loop.
+ ///
+ /// Main server processing loop. Call the processing step routine
+ /// until shut down.
+ ///
+ /// @return The value returned by @c Daemon::getExitValue().
+ int run();
+
+ /// @brief Main server processing step.
+ ///
+ /// Main server processing step. Receives one incoming packet, calls
+ /// the processing packet routing and (if necessary) transmits
+ /// a response.
+ void run_one();
+
+ /// @brief Process a single incoming DHCPv4 packet and sends the response.
+ ///
+ /// It verifies correctness of the passed packet, calls per-type processXXX
+ /// methods, generates appropriate answer, sends the answer to the client.
+ ///
+ /// @param query A pointer to the packet to be processed.
+ void processPacketAndSendResponse(Pkt4Ptr& query);
+
+ /// @brief Process a single incoming DHCPv4 packet and sends the response.
+ ///
+ /// It verifies correctness of the passed packet, calls per-type processXXX
+ /// methods, generates appropriate answer, sends the answer to the client.
+ ///
+ /// @param query A pointer to the packet to be processed.
+ void processPacketAndSendResponseNoThrow(Pkt4Ptr& query);
+
+ /// @brief Process an unparked DHCPv4 packet and sends the response.
+ ///
+ /// @param callout_handle pointer to the callout handle.
+ /// @param query A pointer to the packet to be processed.
+ /// @param rsp A pointer to the response.
+ void sendResponseNoThrow(hooks::CalloutHandlePtr& callout_handle,
+ Pkt4Ptr& query, Pkt4Ptr& rsp);
+
+ /// @brief Process a single incoming DHCPv4 packet.
+ ///
+ /// It verifies correctness of the passed packet, calls per-type processXXX
+ /// methods, generates appropriate answer.
+ ///
+ /// @param query A pointer to the packet to be processed.
+ /// @param rsp A pointer to the response.
+ /// @param allow_packet_park Indicates if parking a packet is allowed.
+ void processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp,
+ bool allow_packet_park = true);
+
+ /// @brief Process a single incoming DHCPv4 query.
+ ///
+ /// It calls per-type processXXX methods, generates appropriate answer.
+ ///
+ /// @param query A pointer to the packet to be processed.
+ /// @param rsp A pointer to the response.
+ /// @param allow_packet_park Indicates if parking a packet is allowed.
+ void processDhcp4Query(Pkt4Ptr& query, Pkt4Ptr& rsp,
+ bool allow_packet_park);
+
+ /// @brief Process a single incoming DHCPv4 query.
+ ///
+ /// It calls per-type processXXX methods, generates appropriate answer,
+ /// sends the answer to the client.
+ ///
+ /// @param query A pointer to the packet to be processed.
+ /// @param rsp A pointer to the response.
+ /// @param allow_packet_park Indicates if parking a packet is allowed.
+ void processDhcp4QueryAndSendResponse(Pkt4Ptr& query, Pkt4Ptr& rsp,
+ bool allow_packet_park);
+
+ /// @brief Instructs the server to shut down.
+ void shutdown() override;
+
+ ///
+ /// @name Public accessors returning values required to (re)open sockets.
+ ///
+ //@{
+ ///
+ /// @brief Get UDP port on which server should listen.
+ ///
+ /// Typically, server listens on UDP port number 67. Other ports are used
+ /// for testing purposes only.
+ ///
+ /// @return UDP port on which server should listen.
+ uint16_t getServerPort() const {
+ return (server_port_);
+ }
+
+ /// @brief Return bool value indicating that broadcast flags should be set
+ /// on sockets.
+ ///
+ /// @return A bool value indicating that broadcast should be used (if true).
+ bool useBroadcast() const {
+ return (use_bcast_);
+ }
+ //@}
+
+ /// @brief Starts DHCP_DDNS client IO if DDNS updates are enabled.
+ ///
+ /// If updates are enabled, it instructs the D2ClientMgr singleton to
+ /// enter send mode. If D2ClientMgr encounters errors it may throw
+ /// D2ClientError. This method does not catch exceptions.
+ void startD2();
+
+ /// @brief Stops DHCP_DDNS client IO if DDNS updates are enabled.
+ ///
+ /// If updates are enabled, it instructs the D2ClientMgr singleton to
+ /// leave send mode. If D2ClientMgr encounters errors it may throw
+ /// D2ClientError. This method does not catch exceptions.
+ void stopD2();
+
+ /// @brief Implements the error handler for DHCP_DDNS IO errors
+ ///
+ /// Invoked when a NameChangeRequest send to kea-dhcp-ddns completes with
+ /// a failed status. These are communications errors, not data related
+ /// failures.
+ ///
+ /// This method logs the failure and then suspends all further updates.
+ /// Updating can only be restored by reconfiguration or restarting the
+ /// server. There is currently no retry logic so the first IO error that
+ /// occurs will suspend updates.
+ /// @todo We may wish to make this more robust or sophisticated.
+ ///
+ /// @param result Result code of the send operation.
+ /// @param ncr NameChangeRequest which failed to send.
+ virtual void d2ClientErrorHandler(const dhcp_ddns::
+ NameChangeSender::Result result,
+ dhcp_ddns::NameChangeRequestPtr& ncr);
+
+ /// @brief Discards parked packets
+ /// Clears the packet parking lots of all packets.
+ /// Called during reconfigure and shutdown.
+ void discardPackets();
+
+ /// @brief Returns value of the test_send_responses_to_source_ flag.
+ ///
+ /// @return value of the test_send_responses_to_source_ flag.
+ bool getSendResponsesToSource() const {
+ return (test_send_responses_to_source_);
+ }
+
+ /// @brief Initialize client context and perform early global
+ /// reservations lookup.
+ ///
+ /// @param query The query message.
+ /// @param ctx Pointer to client context.
+ /// @return true if processing can continue, false if the query must be
+ /// dropped.
+ bool earlyGHRLookup(const Pkt4Ptr& query,
+ AllocEngine::ClientContext4Ptr ctx);
+
+protected:
+
+ /// @name Functions filtering and sanity-checking received messages.
+ ///
+ /// @todo These functions are supposed to be moved to a new class which
+ /// will manage different rules for accepting and rejecting messages.
+ /// Perhaps ticket #3116 is a good opportunity to do it.
+ ///
+ //@{
+ /// @brief Checks whether received message should be processed or discarded.
+ ///
+ /// This function checks whether received message should be processed or
+ /// discarded. It should be called on the beginning of message processing
+ /// (just after the message has been decoded). This message calls a number
+ /// of other functions which check whether message should be processed,
+ /// using different criteria.
+ ///
+ /// This function should be extended when new criteria for accepting
+ /// received message have to be implemented. This function is meant to
+ /// aggregate all early filtering checks on the received message. By having
+ /// a single function like this, we are avoiding bloat of the server's main
+ /// loop.
+ ///
+ /// @warning This function should remain exception safe.
+ ///
+ /// @param query Received message.
+ ///
+ /// @return true if the message should be further processed, or false if
+ /// the message should be discarded.
+ bool accept(const Pkt4Ptr& query) const;
+
+ /// @brief Check if a message sent by directly connected client should be
+ /// accepted or discarded.
+ ///
+ /// This function checks if the received message is from directly connected
+ /// client. If it is, it checks that it should be processed or discarded.
+ ///
+ /// Note that this function doesn't validate all addresses being carried in
+ /// the message. The primary purpose of this function is to filter out
+ /// direct messages in the local network for which there is no suitable
+ /// subnet configured. For example, this function accepts unicast messages
+ /// because unicasts may be used by clients located in remote networks to
+ /// to renew existing leases. If their notion of address is wrong, the
+ /// server will have to sent a NAK, instead of dropping the message.
+ /// Detailed validation of such messages is performed at later stage of
+ /// processing.
+ ///
+ /// This function accepts the following messages:
+ /// - all valid relayed messages,
+ /// - all unicast messages,
+ /// - all broadcast messages except DHCPINFORM received on the interface
+ /// for which the suitable subnet exists (is configured).
+ /// - all DHCPINFORM messages with source address or ciaddr set.
+ ///
+ /// @param query Message sent by a client.
+ ///
+ /// @return true if message is accepted for further processing, false
+ /// otherwise.
+ bool acceptDirectRequest(const Pkt4Ptr& query) const;
+
+ /// @brief Check if received message type is valid for the server to
+ /// process.
+ ///
+ /// This function checks that the received message type belongs to
+ /// the range of types recognized by the server and that the
+ /// message of this type should be processed by the server.
+ ///
+ /// The messages types accepted for processing are:
+ /// - Discover
+ /// - Request
+ /// - Release
+ /// - Decline
+ /// - Inform
+ ///
+ /// @param query Message sent by a client.
+ ///
+ /// @return true if message is accepted for further processing, false
+ /// otherwise.
+ bool acceptMessageType(const Pkt4Ptr& query) const;
+
+ /// @brief Verifies if the server id belongs to our server.
+ ///
+ /// This function checks if the server identifier carried in the specified
+ /// DHCPv4 message belongs to this server. If the server identifier option
+ /// is absent or the value carried by this option is equal to one of the
+ /// server identifiers used by the server, the true is returned. If the
+ /// server identifier option is present, but it doesn't match any server
+ /// identifier used by this server, the false value is returned.
+ ///
+ /// @param pkt DHCPv4 message which server identifier is to be checked.
+ ///
+ /// @return true, if the server identifier is absent or matches one of the
+ /// server identifiers that the server is using; false otherwise.
+ bool acceptServerId(const Pkt4Ptr& pkt) const;
+ //@}
+
+ /// @brief Verifies if specified packet meets RFC requirements
+ ///
+ /// Checks if mandatory option is really there, that forbidden option
+ /// is not there, and that client-id or server-id appears only once.
+ ///
+ /// @param query Pointer to the client's message.
+ /// @param serverid expectation regarding server-id option
+ /// @throw RFCViolation if any issues are detected
+ static void sanityCheck(const Pkt4Ptr& query, RequirementLevel serverid);
+
+ /// @brief Processes incoming DISCOVER and returns response.
+ ///
+ /// Processes received DISCOVER message and verifies that its sender
+ /// should be served. In particular, a lease is selected and sent
+ /// as an offer to a client if it should be served.
+ ///
+ /// @param discover DISCOVER message received from client
+ /// @param context pointer to the client context
+ ///
+ /// @return OFFER message or NULL
+ Pkt4Ptr processDiscover(Pkt4Ptr& discover, AllocEngine::ClientContext4Ptr& context);
+
+ /// @brief Processes incoming REQUEST and returns REPLY response.
+ ///
+ /// Processes incoming REQUEST message and verifies that its sender
+ /// should be served. In particular, verifies that requested lease
+ /// is valid, not expired, not reserved, not used by other client and
+ /// that requesting client is allowed to use it.
+ ///
+ /// Returns ACK message, NAK message, or NULL
+ ///
+ /// @param request a message received from client
+ /// @param context pointer to the client context where allocated and
+ /// deleted leases are stored.
+ ///
+ /// @return ACK or NAK message
+ Pkt4Ptr processRequest(Pkt4Ptr& request, AllocEngine::ClientContext4Ptr& context);
+
+ /// @brief Processes incoming DHCPRELEASE messages.
+ ///
+ /// In DHCPv4, server does not respond to RELEASE messages, therefore
+ /// this function does not return anything.
+ ///
+ /// @param release message received from client
+ /// @param context pointer to the client context where released lease is
+ /// stored.
+ void processRelease(Pkt4Ptr& release, AllocEngine::ClientContext4Ptr& context);
+
+ /// @brief Process incoming DHCPDECLINE messages.
+ ///
+ /// This method processes incoming DHCPDECLINE. In particular, it extracts
+ /// Requested IP Address option, checks that the address really belongs to
+ /// the client and if it does, calls @ref declineLease.
+ ///
+ /// @param decline message received from client
+ /// @param context pointer to the client context where declined lease is
+ /// stored.
+ void processDecline(Pkt4Ptr& decline, AllocEngine::ClientContext4Ptr& context);
+
+ /// @brief Processes incoming DHCPINFORM messages.
+ ///
+ /// @param inform message received from client
+ /// @param context pointer to the client context
+ ///
+ /// @return DHCPACK to be sent to the client.
+ Pkt4Ptr processInform(Pkt4Ptr& inform, AllocEngine::ClientContext4Ptr& context);
+
+ /// @brief Build the configured option list
+ ///
+ /// @note The configured option list is an *ordered* list of
+ /// @c CfgOption objects used to append options to the response.
+ ///
+ /// @param ex The exchange where the configured option list is cached
+ void buildCfgOptionList(Dhcpv4Exchange& ex);
+
+ /// @brief Appends options requested by client.
+ ///
+ /// This method assigns options that were requested by client
+ /// (sent in PRL) or are enforced by server.
+ ///
+ /// @param ex The exchange holding both the client's message and the
+ /// server's response.
+ void appendRequestedOptions(Dhcpv4Exchange& ex);
+
+ /// @brief Appends requested vendor options as requested by client.
+ ///
+ /// This method is similar to \ref appendRequestedOptions(), but uses
+ /// vendor options. The major difference is that vendor-options use
+ /// its own option spaces (there may be more than one distinct set of vendor
+ /// options, each with unique vendor-id). Vendor options are requested
+ /// using separate options within their respective vendor-option spaces.
+ ///
+ /// @param ex The exchange holding both the client's message and the
+ /// server's response.
+ void appendRequestedVendorOptions(Dhcpv4Exchange& ex);
+
+ /// @brief Assigns a lease and appends corresponding options
+ ///
+ /// This method chooses the most appropriate lease for requesting
+ /// client and assigning it. Options corresponding to the lease
+ /// are added to specific message.
+ ///
+ /// This method may reset the pointer to the response in the @c ex object
+ /// to indicate that the response should not be sent to the client.
+ /// The caller must check if the response is is null after calling
+ /// this method.
+ ///
+ /// The response type in the @c ex object may be set to DHCPACK or DHCPNAK.
+ ///
+ /// @param ex DHCPv4 exchange holding the client's message to be checked.
+ void assignLease(Dhcpv4Exchange& ex);
+
+ /// @brief Update client name and DNS flags in the lease and response
+ ///
+ /// There are two cases when the client name (FQDN or hostname) and DNS
+ /// flags need to updated after the lease has been allocated:
+ /// 1. If the name is being generated from the lease address
+ /// 2. If the allocation changed the chosen subnet
+ ///
+ /// In the first case this function will generate the name from the
+ /// lease address. In either case, the name and DNS flags are updated
+ /// in the lease and in the response packet.
+ ///
+ /// @param ctx reference to the client context
+ /// @param lease reference to the client lease
+ /// @param query reference to the client query
+ /// @param resp reference to the client response
+ /// @param client_name_changed - true if the new values are already in
+ /// the lease
+ void postAllocateNameUpdate(const AllocEngine::ClientContext4Ptr& ctx,
+ const Lease4Ptr& lease, const Pkt4Ptr& query,
+ const Pkt4Ptr& resp, bool client_name_changed);
+
+ /// @brief Adds the T1 and T2 timers to the outbound response as appropriate
+ ///
+ /// This method determines if either of the timers T1 (option 58) and T2
+ /// (option 59) should be sent to the client. It is influenced by the
+ /// lease's subnet's values for renew-timer, rebind-timer,
+ /// calculate-tee-times, t1-percent, and t2-percent as follows:
+ ///
+ /// By default neither T1 nor T2 will be sent.
+ ///
+ /// T2:
+ ///
+ /// If rebind-timer is set use its value, otherwise if calculate-tee-times
+ /// is true use the value given by valid lease time * t2-percent. Either
+ /// way the value will only be sent if it is less than the valid lease time.
+ ///
+ /// T1:
+ ///
+ /// If renew-timer is set use its value, otherwise if calculate-tee-times
+ /// is true use the value given by valid lease time * t1-percent. Either
+ /// way the value will only be sent if it is less than T2 when T2 is being
+ /// sent, or less than the valid lease time if T2 is not being sent.
+ ///
+ /// @param lease lease being assigned to the client
+ /// @param subnet the subnet to which the lease belongs
+ /// @param resp outbound response for the client to which timers are added.
+ void setTeeTimes(const Lease4Ptr& lease, const Subnet4Ptr& subnet, Pkt4Ptr resp);
+
+ /// @brief Append basic options if they are not present.
+ ///
+ /// This function adds the following basic options if they
+ /// are not yet added to the response message:
+ /// - Subnet Mask,
+ /// - Router,
+ /// - Name Server,
+ /// - Domain Name,
+ /// - Server Identifier.
+ ///
+ /// @param ex DHCPv4 exchange holding the client's message to be checked.
+ void appendBasicOptions(Dhcpv4Exchange& ex);
+
+ /// @brief Sets fixed fields of the outgoing packet.
+ ///
+ /// If the incoming packets belongs to a class and that class defines
+ /// next-server, server-hostname or boot-file-name, we need to set the
+ /// siaddr, sname or filename fields in the outgoing packet. Also, those
+ /// values can be defined for subnet or in reservations. The values
+ /// defined in reservation takes precedence over class values, which
+ /// in turn take precedence over subnet values.
+ ///
+ /// @param ex DHCPv4 exchange holding the client's message and the server's
+ /// response to be adjusted.
+ void setFixedFields(Dhcpv4Exchange& ex);
+
+ /// @brief Processes Client FQDN and Hostname Options sent by a client.
+ ///
+ /// Client may send Client FQDN or Hostname option to communicate its name
+ /// to the server. Server may use this name to perform DNS update for the
+ /// lease being assigned to a client. If server takes responsibility for
+ /// updating DNS for a client it may communicate it by sending the Client
+ /// FQDN or Hostname %Option back to the client. Server select a different
+ /// name than requested by a client to update DNS. In such case, the server
+ /// stores this different name in its response.
+ ///
+ /// Client should not send both Client FQDN and Hostname options. However,
+ /// if client sends both options, server should prefer Client FQDN option
+ /// and ignore the Hostname option. If Client FQDN option is not present,
+ /// the Hostname option is processed.
+ ///
+ /// The Client FQDN %Option is processed by this function as described in
+ /// RFC4702.
+ ///
+ /// In response to a Hostname %Option sent by a client, the server may send
+ /// Hostname option with the same or different hostname. If different
+ /// hostname is sent, it is an indication to the client that server has
+ /// overridden the client's preferred name and will rather use this
+ /// different name to update DNS. However, since Hostname option doesn't
+ /// carry an information whether DNS update will be carried by the server
+ /// or not, the client is responsible for checking whether DNS update
+ /// has been performed.
+ ///
+ /// After successful processing options stored in the first parameter,
+ /// this function may add Client FQDN or Hostname option to the response
+ /// message. In some cases, server may cease to add any options to the
+ /// response, i.e. when server doesn't support DNS updates.
+ ///
+ /// This function does not throw. It simply logs the debug message if the
+ /// processing of the FQDN or Hostname failed.
+ ///
+ /// @param ex The exchange holding both the client's message and the
+ /// server's response.
+ void processClientName(Dhcpv4Exchange& ex);
+
+ /// @brief This function sets statistics related to DHCPv4 packets processing
+ /// to their initial values.
+ ///
+ /// All of the statistics observed by the DHCPv4 server and with the names
+ /// like "pkt4-" are reset to 0. This function must be invoked in the class
+ /// constructor.
+ void setPacketStatisticsDefaults();
+
+ /// @brief Sets value of the test_send_responses_to_source_ flag.
+ ///
+ /// @param value new value of the test_send_responses_to_source_ flag.
+ void setSendResponsesToSource(bool value) {
+ test_send_responses_to_source_ = value;
+ }
+
+public:
+
+ /// @brief this is a prefix added to the content of vendor-class option
+ ///
+ /// If incoming packet has a vendor class option, its content is
+ /// prepended with this prefix and then interpreted as a class.
+ /// For example, a packet that sends vendor class with value of "FOO"
+ /// will cause the packet to be assigned to class VENDOR_CLASS_FOO.
+ static const std::string VENDOR_CLASS_PREFIX;
+
+private:
+ /// @brief Process Client FQDN %Option sent by a client.
+ ///
+ /// This function is called by the @c Dhcpv4Srv::processClientName when
+ /// the client has sent the FQDN option in its message to the server.
+ /// It comprises the actual logic to parse the FQDN option and prepare
+ /// the FQDN option to be sent back to the client in the server's
+ /// response.
+ ///
+ /// @param ex The exchange holding both the client's message and the
+ /// server's response.
+ void processClientFqdnOption(Dhcpv4Exchange& ex);
+
+ /// @brief Process Hostname %Option sent by a client.
+ ///
+ /// This method is called by the @c Dhcpv4Srv::processClientName to
+ /// create an instance of the Hostname option to be returned to the
+ /// client. If this instance is created it is included in the response
+ /// message within the @c Dhcpv4Exchange object passed as an argument.
+ ///
+ /// The Hostname option instance is created if the client has included
+ /// Hostname option in its query to the server or if the client has
+ /// included Hostname option code in the Parameter Request List option.
+ /// In the former case, the server can use the Hostname supplied by the
+ /// client or replace it with a new hostname, depending on the server's
+ /// configuration. A reserved hostname takes precedence over a hostname
+ /// supplied by the client or auto generated hostname.
+ ///
+ /// If the 'qualifying-suffix' parameter is specified, its value is used
+ /// to qualify a hostname. For example, if the host reservation contains
+ /// a hostname 'marcin-laptop', and the qualifying suffix is
+ /// 'example.isc.org', the hostname returned to the client will be
+ /// 'marcin-laptop.example.isc.org'. If the 'qualifying-suffix' is not
+ /// specified (empty), the reserved hostname is returned to the client
+ /// unqualified.
+ ///
+ /// The 'qualifying-suffix' value is also used to qualify the hostname
+ /// supplied by the client, when this hostname is unqualified,
+ /// e.g. 'laptop-x'. If the supplied hostname is qualified, e.g.
+ /// 'laptop-x.example.org', the qualifying suffix will not be appended
+ /// to it.
+ ///
+ /// @param ex The exchange holding both the client's message and the
+ /// server's response.
+ void processHostnameOption(Dhcpv4Exchange& ex);
+
+ /// @public
+ /// @brief Marks lease as declined.
+ ///
+ /// This method moves a lease to declined state with all the steps involved:
+ /// - trigger DNS removal (if necessary)
+ /// - disassociate the client information
+ /// - update lease in the database (switch to DECLINED state)
+ /// - increase necessary statistics
+ /// - call lease4_decline hook
+ ///
+ /// @param lease lease to be declined
+ /// @param decline client's message
+ /// @param context reference to a client context
+ void declineLease(const Lease4Ptr& lease, const Pkt4Ptr& decline,
+ AllocEngine::ClientContext4Ptr& context);
+
+protected:
+
+ /// @brief Creates NameChangeRequests which correspond to the lease
+ /// which has been acquired.
+ ///
+ /// If this function is called when an existing lease is renewed, it
+ /// may generate NameChangeRequest to remove existing DNS entries which
+ /// correspond to the old lease instance. This function may cease to
+ /// generate NameChangeRequests if the notion of the client's FQDN hasn't
+ /// changed between an old and new lease.
+ ///
+ /// @param lease A pointer to the new lease which has been acquired.
+ /// @param old_lease A pointer to the instance of the old lease which has
+ /// @param ddns_params DDNS configuration parameters
+ /// been replaced by the new lease passed in the first argument. The NULL
+ /// value indicates that the new lease has been allocated, rather than
+ /// lease being renewed.
+ void createNameChangeRequests(const Lease4Ptr& lease,
+ const Lease4Ptr& old_lease,
+ const DdnsParams& ddns_params);
+
+ /// @brief Attempts to renew received addresses
+ ///
+ /// Attempts to renew existing lease. This typically includes finding a lease that
+ /// corresponds to the received address. If no such lease is found, a status code
+ /// response is generated.
+ ///
+ /// @param renew client's message asking for renew
+ /// @param reply server's response (ACK or NAK)
+ void renewLease(const Pkt4Ptr& renew, Pkt4Ptr& reply);
+
+ /// @brief Adds server identifier option to the server's response.
+ ///
+ /// This method adds a server identifier to the DHCPv4 message if it doesn't
+ /// exist yet. This is set to the local address on which the client's query has
+ /// been received with the exception of broadcast traffic and DHCPv4o6 query for
+ /// which a socket on the particular interface is found and its address is used
+ /// as server id.
+ ///
+ /// @note This method doesn't throw exceptions by itself but the underlying
+ /// classes being used my throw. The reason for this method to not sanity
+ /// check the specified message is that it is meant to be called internally
+ /// by the @c Dhcpv4Srv class.
+ ///
+ /// @note This method is static because it is not dependent on the class
+ /// state.
+ ///
+ /// @param ex The exchange holding both the client's message and the
+ /// server's response.
+ static void appendServerID(Dhcpv4Exchange& ex);
+
+ /// @brief Check if the relay port RAI sub-option was set in the query.
+ ///
+ /// @param ex The exchange holding the client's message
+ /// @return the port to use to join the relay or 0 for the default
+ static uint16_t checkRelayPort(const Dhcpv4Exchange& ex);
+
+ /// @brief Set IP/UDP and interface parameters for the DHCPv4 response.
+ ///
+ /// This method sets the following parameters for the DHCPv4 message being
+ /// sent to a client:
+ /// - client unicast or a broadcast address,
+ /// - client or relay port,
+ /// - server address,
+ /// - server port,
+ /// - name and index of the interface which is to be used to send the
+ /// message.
+ ///
+ /// Internally it calls the @c Dhcpv4Srv::adjustRemoteAddr to figure
+ /// out the destination address (client unicast address or broadcast
+ /// address).
+ ///
+ /// The destination port is always DHCPv4 client (68) or relay (67) port,
+ /// depending if the response will be sent directly to a client, unless
+ /// a client port was enforced from the command line.
+ ///
+ /// The source port is always set to DHCPv4 server port (67).
+ ///
+ /// The interface selected for the response is always the same as the
+ /// one through which the query has been received.
+ ///
+ /// The source address for the response is the IPv4 address assigned to
+ /// the interface being used to send the response. This function uses
+ /// @c IfaceMgr to get the socket bound to the IPv4 address on the
+ /// particular interface.
+ ///
+ /// @note This method is static because it is not dependent on the class
+ /// state.
+ ///
+ /// @param ex The exchange holding both the client's message and the
+ /// server's response.
+ void adjustIfaceData(Dhcpv4Exchange& ex);
+
+ /// @brief Sets remote addresses for outgoing packet.
+ ///
+ /// This method sets the local and remote addresses on outgoing packet.
+ /// The addresses being set depend on the following conditions:
+ /// - has incoming packet been relayed,
+ /// - is direct response to a client without address supported,
+ /// - type of the outgoing packet,
+ /// - broadcast flag set in the incoming packet.
+ ///
+ /// @warning This method does not check whether provided packet pointers
+ /// are valid. Make sure that pointers are correct before calling this
+ /// function.
+ ///
+ /// @param ex The exchange holding both the client's message and the
+ /// server's response.
+ void adjustRemoteAddr(Dhcpv4Exchange& ex);
+
+ /// @brief converts server-id to text
+ /// Converts content of server-id option to a text representation, e.g.
+ /// "192.0.2.1"
+ ///
+ /// @param opt option that contains server-id
+ /// @return string representation
+ static std::string srvidToString(const OptionPtr& opt);
+
+ /// @brief Selects a subnet for a given client's packet.
+ ///
+ /// If selectSubnet is called to simply do sanity checks (check if a
+ /// subnet would be selected), then there is no need to call hooks,
+ /// as this will happen later (when selectSubnet is called again).
+ /// In such case the sanity_only should be set to true.
+ ///
+ /// @param query client's message
+ /// @param drop if it is true the packet will be dropped
+ /// @param sanity_only if it is true the callout won't be called
+ /// @return selected subnet (or NULL if no suitable subnet was found)
+ isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& query,
+ bool& drop,
+ bool sanity_only = false) const;
+
+ /// @brief Selects a subnet for a given client's DHCP4o6 packet.
+ ///
+ /// If selectSubnet is called to simply do sanity checks (check if a
+ /// subnet would be selected), then there is no need to call hooks,
+ /// as this will happen later (when selectSubnet is called again).
+ /// In such case the sanity_only should be set to true.
+ ///
+ /// @param query client's message
+ /// @param drop if it is true the packet will be dropped
+ /// @param sanity_only if it is true the callout won't be called
+ /// @return selected subnet (or NULL if no suitable subnet was found)
+ isc::dhcp::Subnet4Ptr selectSubnet4o6(const Pkt4Ptr& query,
+ bool& drop,
+ bool sanity_only = false) const;
+
+ /// @brief dummy wrapper around IfaceMgr::receive4
+ ///
+ /// This method is useful for testing purposes, where its replacement
+ /// simulates reception of a packet. For that purpose it is protected.
+ virtual Pkt4Ptr receivePacket(int timeout);
+
+ /// @brief dummy wrapper around IfaceMgr::send()
+ ///
+ /// This method is useful for testing purposes, where its replacement
+ /// simulates transmission of a packet. For that purpose it is protected.
+ virtual void sendPacket(const Pkt4Ptr& pkt);
+
+ /// @brief Assigns incoming packet to zero or more classes.
+ ///
+ /// @note This is done in two phases: first the content of the
+ /// vendor-class-identifier option is used as a class, by
+ /// calling @ref classifyByVendor(). Second classification match
+ /// expressions are evaluated. The resulting classes will be stored
+ /// in the packet (see @ref isc::dhcp::Pkt4::classes_ and
+ /// @ref isc::dhcp::Pkt4::inClass).
+ ///
+ /// @param pkt packet to be classified
+ void classifyPacket(const Pkt4Ptr& pkt);
+
+protected:
+
+ /// @brief Assigns incoming packet to zero or more classes (required pass).
+ ///
+ /// @note This required classification evaluates all classes which
+ /// were marked for required evaluation. Classes are collected so
+ /// evaluated in the reversed order than output option processing.
+ ///
+ /// @note The only-if-required flag is related because it avoids
+ /// double evaluation (which is not forbidden).
+ ///
+ /// @param ex The exchange holding needed information.
+ void requiredClassify(Dhcpv4Exchange& ex);
+
+ /// @brief Perform deferred option unpacking.
+ ///
+ /// @note Options 43 and 224-254 are processed after classification.
+ /// If a class configures a definition it is applied, if none
+ /// the global (user) definition is applied. For option 43
+ /// a last resort definition (same definition as used in previous Kea
+ /// versions) is applied when none is found.
+ ///
+ /// @param query Pointer to the client message.
+ void deferredUnpack(Pkt4Ptr& query);
+
+ /// @brief Executes pkt4_send callout.
+ ///
+ /// @param callout_handle pointer to the callout handle.
+ /// @param query Pointer to a query.
+ /// @param rsp Pointer to a response.
+ void processPacketPktSend(hooks::CalloutHandlePtr& callout_handle,
+ Pkt4Ptr& query, Pkt4Ptr& rsp);
+
+ /// @brief Executes buffer4_send callout and sends the response.
+ ///
+ /// @param callout_handle pointer to the callout handle.
+ /// @param rsp pointer to a response.
+ void processPacketBufferSend(hooks::CalloutHandlePtr& callout_handle,
+ Pkt4Ptr& rsp);
+
+private:
+
+ /// @public
+ /// @brief Assign class using vendor-class-identifier option
+ ///
+ /// @note This is the first part of @ref classifyPacket
+ ///
+ /// @param pkt packet to be classified
+ void classifyByVendor(const Pkt4Ptr& pkt);
+
+ /// @private
+ /// @brief Constructs netmask option based on subnet4
+ /// @param subnet subnet for which the netmask will be calculated
+ ///
+ /// @return Option that contains netmask information
+ static OptionPtr getNetmaskOption(const Subnet4Ptr& subnet);
+
+protected:
+
+ /// UDP port number on which server listens.
+ uint16_t server_port_;
+
+ /// UDP port number to which server sends all responses.
+ uint16_t client_port_;
+
+ /// Indicates if shutdown is in progress. Setting it to true will
+ /// initiate server shutdown procedure.
+ volatile bool shutdown_;
+
+ /// @brief Allocation Engine.
+ /// Pointer to the allocation engine that we are currently using
+ /// It must be a pointer, because we will support changing engines
+ /// during normal operation (e.g. to use different allocators)
+ boost::shared_ptr<AllocEngine> alloc_engine_;
+
+ /// Should broadcast be enabled on sockets (if true).
+ bool use_bcast_;
+
+ /// @brief Holds information about disabled DHCP service and/or
+ /// disabled subnet/network scopes.
+ NetworkStatePtr network_state_;
+
+ /// @brief Controls access to the configuration backends.
+ CBControlDHCPv4Ptr cb_control_;
+
+private:
+
+ /// @brief store value that defines if kea will send responses
+ /// to a source address of incoming packet. Only for testing.
+ bool test_send_responses_to_source_;
+
+public:
+
+ /// Class methods for DHCPv4-over-DHCPv6 handler
+
+ /// @brief Updates statistics for received packets
+ /// @param query packet received
+ static void processStatsReceived(const Pkt4Ptr& query);
+
+ /// @brief Updates statistics for transmitted packets
+ /// @param response packet transmitted
+ static void processStatsSent(const Pkt4Ptr& response);
+
+ /// @brief Returns the index for "buffer4_receive" hook point
+ /// @return the index for "buffer4_receive" hook point
+ static int getHookIndexBuffer4Receive();
+
+ /// @brief Returns the index for "pkt4_receive" hook point
+ /// @return the index for "pkt4_receive" hook point
+ static int getHookIndexPkt4Receive();
+
+ /// @brief Returns the index for "subnet4_select" hook point
+ /// @return the index for "subnet4_select" hook point
+ static int getHookIndexSubnet4Select();
+
+ /// @brief Returns the index for "lease4_release" hook point
+ /// @return the index for "lease4_release" hook point
+ static int getHookIndexLease4Release();
+
+ /// @brief Returns the index for "pkt4_send" hook point
+ /// @return the index for "pkt4_send" hook point
+ static int getHookIndexPkt4Send();
+
+ /// @brief Returns the index for "buffer4_send" hook point
+ /// @return the index for "buffer4_send" hook point
+ static int getHookIndexBuffer4Send();
+
+ /// @brief Returns the index for "lease4_decline" hook point
+ /// @return the index for "lease4_decline" hook point
+ static int getHookIndexLease4Decline();
+
+ /// @brief Return a list of all paths that contain passwords or secrets for
+ /// kea-dhcp4.
+ ///
+ /// @return the list of lists of sequential JSON map keys needed to reach
+ /// the passwords and secrets.
+ std::list<std::list<std::string>> jsonPathsToRedact() const final override;
+};
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // DHCP4_SRV_H
diff --git a/src/bin/dhcp4/dhcp4o6.dox b/src/bin/dhcp4/dhcp4o6.dox
new file mode 100644
index 0000000..134a235
--- /dev/null
+++ b/src/bin/dhcp4/dhcp4o6.dox
@@ -0,0 +1,62 @@
+// Copyright (C) 2016 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/.
+
+/**
+ @page dhcpv4o6Dhcp4 DHCPv4-over-DHCPv6 DHCPv4 Server Side
+
+Kea supports DHCPv4-over-DHCPv6 using cooperating DHCPv6 and DHCPv4
+servers. This section describes the DHCPv4 server side. For its
+DHCPv6 counter-part, see @ref dhcpv4o6Dhcp6.
+
+@section Dhcp4to6Ipc DHCPv6-to-DHCPv4 Inter Process Communication
+
+The @c Dhcp4to6Ipc class is derived from the base @c Dhcp4o6IpcBase as
+a singleton class (by the static @ref isc::dhcp::Dhcp4to6Ipc::instance
+function). @ref isc::dhcp::Dhcp4to6Ipc::open is called to open IPC
+UDP sockets and to register @ref isc::dhcp::Dhcp4to6Ipc::handler on
+external sockets on the @c IfaceMgr.
+
+@section dhcp4to6Receive DHCPv4-over-DHCPv6 Packet Processing
+
+DHCPv6 DHCPv4-QUERY messages are forwarded by the DHCPv6 server on the IPC.
+The interface manager (@c IfaceMgr) using the external socket mechanism
+invokes @ref isc::dhcp::Dhcp4to6Ipc::handler, the packet is received using
+(inherited) @ref isc::dhcp::Dhcp4o6IpcBase::receive which decodes and strips
+the ISC Vendor option.
+
+The handler verifies there is one and only one DHCPv4-Message option
+and extracts it. @ref isc::dhcp::Dhcpv4Srv::processPacket processes
+the DHCPv4 query as a @c Pkt4o6 instance and builds the
+DHCPv4-over-DHCPv6 response.
+Registered callouts for "buffer4_send" are called (@ref
+dhcpv4HooksBuffer4Send, note all the other DHCPv4 hook points are
+served during the standard processing). The response is sent back
+to the DHCPv6 server on the IPC.
+
+@section dhcp4to6Specific Modified DHCPv4 Routines
+
+For a @c Pkt4o6 query the @ref isc::dhcp::Dhcpv4Exchange::initResponse
+performs some extra steps (@ref isc::dhcp::Dhcpv4Exchange::initResponse4o6):
+the DHCPv6 response is built and the response member is reset to
+a @c Pkt4o6 instance with DHCPv4 and DHCPv6 parts.
+
+The subnet selection (@ref isc::dhcp::Dhcpv4Srv::selectSubnet) is specialized
+for @c Pkt4o6 queries (@ref isc::dhcp::Dhcpv4Srv::selectSubnet4o6):
+the subnet selector class (@c SubnetSelector) is filled using DHCPv4 and
+DHCPv6 information and the @ref isc::dhcp::CfgSubnets4::selectSubnet4o6
+function is called (instead of @ref isc::dhcp::CfgSubnets4::selectSubnet).
+
+In @ref isc::dhcp::Dhcpv4Srv::adjustIfaceData for @c Pkt4o6 queries
+the local address is set to the incoming interface assigned address
+(same case than for a broadcast local address).
+
+In @ref isc::dhcp::Dhcpv4Srv::adjustRemoteAddr for @c Pkt4o6 queries the
+remote address is set to the query one (which is in fact an IPv6 address).
+
+In @ref isc::dhcp::Dhcpv4Srv::acceptDirectRequest @c Pkt4o6 queries are
+accepted (they are considered as being relayed).
+
+*/
diff --git a/src/bin/dhcp4/dhcp4to6_ipc.cc b/src/bin/dhcp4/dhcp4to6_ipc.cc
new file mode 100644
index 0000000..074fde6
--- /dev/null
+++ b/src/bin/dhcp4/dhcp4to6_ipc.cc
@@ -0,0 +1,191 @@
+// 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 <util/buffer.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt4o6.h>
+#include <dhcp/pkt6.h>
+#include <dhcpsrv/callout_handle_store.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/dhcp4to6_ipc.h>
+#include <dhcp4/dhcp4_log.h>
+#include <hooks/callout_handle.h>
+#include <hooks/hooks_log.h>
+#include <hooks/hooks_manager.h>
+
+using namespace std;
+using namespace isc::dhcp;
+using namespace isc::hooks;
+
+namespace isc {
+namespace dhcp {
+
+Dhcp4to6Ipc::Dhcp4to6Ipc() : Dhcp4o6IpcBase() {}
+
+Dhcp4to6Ipc& Dhcp4to6Ipc::instance() {
+ static Dhcp4to6Ipc dhcp4to6_ipc;
+ return (dhcp4to6_ipc);
+}
+
+void Dhcp4to6Ipc::open() {
+ uint16_t port = CfgMgr::instance().getStagingCfg()->getDhcp4o6Port();
+ if (port == 0) {
+ Dhcp4o6IpcBase::close();
+ return;
+ }
+ if (port > 65534) {
+ isc_throw(OutOfRange, "DHCP4o6 port " << port);
+ }
+
+ int old_fd = socket_fd_;
+ socket_fd_ = Dhcp4o6IpcBase::open(port, ENDPOINT_TYPE_V4);
+ if ((old_fd == -1) && (socket_fd_ != old_fd)) {
+ IfaceMgr::instance().addExternalSocket(socket_fd_,
+ Dhcp4to6Ipc::handler);
+ }
+}
+
+void Dhcp4to6Ipc::handler(int /* fd */) {
+ Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance();
+ Pkt6Ptr pkt;
+
+ try {
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL, DHCP4_DHCP4O6_RECEIVING);
+ // Receive message from the IPC socket.
+ pkt = ipc.receive();
+
+ // from Dhcpv4Srv::run_one() after receivePacket()
+ if (pkt) {
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC, DHCP4_DHCP4O6_PACKET_RECEIVED)
+ .arg(static_cast<int>(pkt->getType()))
+ .arg(pkt->getRemoteAddr().toText())
+ .arg(pkt->getRemotePort())
+ .arg(pkt->getIface());
+ }
+ } catch (const std::exception& e) {
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL, DHCP4_DHCP4O6_RECEIVE_FAIL)
+ .arg(e.what());
+ }
+
+ if (!pkt) {
+ return;
+ }
+
+ // Each message must contain option holding DHCPv4 message.
+ OptionCollection msgs = pkt->getOptions(D6O_DHCPV4_MSG);
+ if (msgs.empty()) {
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL, DHCP4_DHCP4O6_BAD_PACKET)
+ .arg("DHCPv4 message option not present");
+ return;
+ } else if (msgs.size() > 1) {
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL, DHCP4_DHCP4O6_BAD_PACKET)
+ .arg("more than one DHCPv4 message option");
+ return;
+ }
+
+ // Get the DHCPv4 message.
+ OptionPtr msg = msgs.begin()->second;
+ if (!msg) {
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL, DHCP4_DHCP4O6_BAD_PACKET)
+ .arg("null DHCPv4 message option");
+ return;
+ }
+
+ // Extract the DHCPv4 packet with DHCPv6 packet attached
+ Pkt4Ptr query(new Pkt4o6(msg->getData(), pkt));
+
+ // From Dhcpv4Srv::run_one() processing and after
+ Pkt4Ptr rsp;
+
+ ControlledDhcpv4Srv::getInstance()->processPacket(query, rsp, false);
+
+ if (!rsp) {
+ return;
+ }
+
+ try {
+ // Now all fields and options are constructed into output wire buffer.
+ // Option objects modification does not make sense anymore. Hooks
+ // can only manipulate wire buffer at this stage.
+ // Let's execute all callouts registered for buffer4_send
+ if (HooksManager::calloutsPresent(Dhcpv4Srv::getHookIndexBuffer4Send())) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+ // Delete previously set arguments
+ callout_handle->deleteAllArguments();
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+ // Enable copying options from the packet within hook library.
+ ScopedEnableOptionsCopy<Pkt4> response4_options_copy(rsp);
+
+ // Pass incoming packet as argument
+ callout_handle->setArgument("response4", rsp);
+
+ // Call callouts
+ HooksManager::callCallouts(Dhcpv4Srv::getHookIndexBuffer4Send(),
+ *callout_handle);
+
+ // Callouts decided to skip the next processing step. The next
+ // processing step would to parse the packet, so skip at this
+ // stage means drop.
+ if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
+ (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
+ LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
+ DHCP4_HOOK_BUFFER_SEND_SKIP)
+ .arg(rsp->getLabel());
+ return;
+ }
+
+ callout_handle->getArgument("response4", rsp);
+ }
+
+ Pkt4o6Ptr rsp6 = boost::dynamic_pointer_cast<Pkt4o6>(rsp);
+ // Should not happen
+ if (!rsp6) {
+ isc_throw(Unexpected, "Dhcp4o6 packet cast fail");
+ }
+
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC, DHCP4_DHCP4O6_PACKET_SEND)
+ .arg(rsp6->getLabel())
+ .arg(rsp6->getName())
+ .arg(static_cast<int>(rsp6->getType()))
+ .arg(rsp6->getRemoteAddr())
+ .arg(rsp6->getRemotePort())
+ .arg(rsp6->getIface())
+ .arg(rsp->getLabel())
+ .arg(rsp->getName())
+ .arg(static_cast<int>(rsp->getType()));
+
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA,
+ DHCP4_DHCP4O6_RESPONSE_DATA)
+ .arg(rsp6->getLabel())
+ .arg(rsp6->getName())
+ .arg(static_cast<int>(rsp6->getType()))
+ .arg(rsp6->toText());
+
+ ipc.send(rsp6->getPkt6());
+
+ // Update statistics accordingly for sent packet.
+ Dhcpv4Srv::processStatsSent(rsp);
+
+ } catch (const std::exception& e) {
+ LOG_ERROR(packet4_logger, DHCP4_DHCP4O6_PACKET_SEND_FAIL)
+ .arg(rsp->getLabel())
+ .arg(e.what());
+ }
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/bin/dhcp4/dhcp4to6_ipc.h b/src/bin/dhcp4/dhcp4to6_ipc.h
new file mode 100644
index 0000000..a2e86f0
--- /dev/null
+++ b/src/bin/dhcp4/dhcp4to6_ipc.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2015-2019 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 DHCP4TO6_IPC_H
+#define DHCP4TO6_IPC_H
+
+/// @file dhcp4to6_ipc.h Defines the Dhcp4o6Ipc class.
+/// This file defines the class Kea uses to act as the DHCPv4 server
+/// side of DHCPv4-over-DHCPv6 communication between servers.
+///
+
+#include <dhcp/pkt4o6.h>
+#include <dhcpsrv/dhcp4o6_ipc.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Handles DHCPv4-over-DHCPv6 IPC on the DHCPv4 server side
+class Dhcp4to6Ipc : public Dhcp4o6IpcBase {
+protected:
+ /// @brief Constructor
+ ///
+ /// Default constructor
+ Dhcp4to6Ipc();
+
+ /// @brief Destructor.
+ virtual ~Dhcp4to6Ipc() { }
+
+public:
+ /// @brief Returns pointer to the sole instance of Dhcp4to6Ipc
+ ///
+ /// Dhcp4to6Ipc is a singleton class
+ ///
+ /// @return the only existing instance of DHCP4o6 IPC
+ static Dhcp4to6Ipc& instance();
+
+ /// @brief Open communication socket
+ ///
+ /// Call base open method and sets the handler/callback when needed
+ virtual void open();
+
+ /// @brief On receive handler
+ ///
+ /// The handler processes the DHCPv4-query DHCPv6 packet and
+ /// sends the DHCPv4-response DHCPv6 packet back to the DHCPv6 server
+ static void handler(int /* fd */);
+};
+
+} // namespace isc
+} // namespace dhcp
+
+#endif
diff --git a/src/bin/dhcp4/json_config_parser.cc b/src/bin/dhcp4/json_config_parser.cc
new file mode 100644
index 0000000..4816fcc
--- /dev/null
+++ b/src/bin/dhcp4/json_config_parser.cc
@@ -0,0 +1,836 @@
+// Copyright (C) 2012-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 <config/command_mgr.h>
+#include <database/dbaccess_parser.h>
+#include <database/backend_selector.h>
+#include <database/server_selector.h>
+#include <dhcp4/dhcp4_log.h>
+#include <dhcp4/dhcp4_srv.h>
+#include <dhcp4/json_config_parser.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/cb_ctl_dhcp4.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/config_backend_dhcp4_mgr.h>
+#include <dhcpsrv/db_type.h>
+#include <dhcpsrv/parsers/client_class_def_parser.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <dhcpsrv/parsers/expiration_config_parser.h>
+#include <dhcpsrv/parsers/host_reservation_parser.h>
+#include <dhcpsrv/parsers/host_reservations_list_parser.h>
+#include <dhcpsrv/parsers/ifaces_config_parser.h>
+#include <dhcpsrv/parsers/multi_threading_config_parser.h>
+#include <dhcpsrv/parsers/option_data_parser.h>
+#include <dhcpsrv/parsers/dhcp_queue_control_parser.h>
+#include <dhcpsrv/parsers/simple_parser4.h>
+#include <dhcpsrv/parsers/shared_networks_list_parser.h>
+#include <dhcpsrv/parsers/sanity_checks_parser.h>
+#include <dhcpsrv/host_data_source_factory.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/hooks_parser.h>
+#include <process/config_ctl_parser.h>
+#include <util/encode/hex.h>
+#include <util/multi_threading_mgr.h>
+#include <util/strutil.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <iomanip>
+#include <iostream>
+#include <limits>
+#include <map>
+#include <netinet/in.h>
+#include <vector>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::hooks;
+using namespace isc::process;
+using namespace isc::config;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Parser that takes care of global DHCPv4 parameters and utility
+/// functions that work on global level.
+///
+/// This class is a collection of utility method that either handle
+/// global parameters (see @ref parse), or conducts operations on
+/// global level (see @ref sanityChecks and @ref copySubnets4).
+///
+/// See @ref parse method for a list of supported parameters.
+class Dhcp4ConfigParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief Sets global parameters in staging configuration
+ ///
+ /// @param global global configuration scope
+ /// @param cfg Server configuration (parsed parameters will be stored here)
+ ///
+ /// Currently this method sets the following global parameters:
+ ///
+ /// - echo-client-id
+ /// - decline-probation-period
+ /// - dhcp4o6-port
+ /// - user-context
+ ///
+ /// @throw DhcpConfigError if parameters are missing or
+ /// or having incorrect values.
+ void parse(const SrvConfigPtr& cfg, const ConstElementPtr& global) {
+
+ // Set whether v4 server is supposed to echo back client-id
+ // (yes = RFC6842 compatible, no = backward compatibility)
+ bool echo_client_id = getBoolean(global, "echo-client-id");
+ cfg->setEchoClientId(echo_client_id);
+
+ // Set the probation period for decline handling.
+ uint32_t probation_period =
+ getUint32(global, "decline-probation-period");
+ cfg->setDeclinePeriod(probation_period);
+
+ // Set the DHCPv4-over-DHCPv6 interserver port.
+ uint16_t dhcp4o6_port = getUint16(global, "dhcp4o6-port");
+ cfg->setDhcp4o6Port(dhcp4o6_port);
+
+ // Set the global user context.
+ ConstElementPtr user_context = global->get("user-context");
+ if (user_context) {
+ cfg->setContext(user_context);
+ }
+
+ // Set the server's logical name
+ std::string server_tag = getString(global, "server-tag");
+ cfg->setServerTag(server_tag);
+ }
+
+ /// @brief Sets global parameters before other parameters are parsed.
+ ///
+ /// This method sets selected global parameters before other parameters
+ /// are parsed. This is important when the behavior of the parsers
+ /// run later depends on these global parameters.
+ ///
+ /// Currently this method sets the following global parameters:
+ /// - ip-reservations-unique
+ ///
+ /// @param global global configuration scope
+ /// @param cfg Server configuration (parsed parameters will be stored here)
+ void parseEarly(const SrvConfigPtr& cfg, const ConstElementPtr& global) {
+ // Set ip-reservations-unique flag.
+ bool ip_reservations_unique = getBoolean(global, "ip-reservations-unique");
+ cfg->setIPReservationsUnique(ip_reservations_unique);
+ }
+
+ /// @brief Copies subnets from shared networks to regular subnets container
+ ///
+ /// @param from pointer to shared networks container (copy from here)
+ /// @param dest pointer to cfg subnets4 (copy to here)
+ /// @throw BadValue if any pointer is missing
+ /// @throw DhcpConfigError if there are duplicates (or other subnet defects)
+ void
+ copySubnets4(const CfgSubnets4Ptr& dest, const CfgSharedNetworks4Ptr& from) {
+
+ if (!dest || !from) {
+ isc_throw(BadValue, "Unable to copy subnets: at least one pointer is null");
+ }
+
+ const SharedNetwork4Collection* networks = from->getAll();
+ if (!networks) {
+ // Nothing to copy. Technically, it should return a pointer to empty
+ // container, but let's handle null pointer as well.
+ return;
+ }
+
+ // Let's go through all the networks one by one
+ for (auto net = networks->begin(); net != networks->end(); ++net) {
+
+ // For each network go through all the subnets in it.
+ const Subnet4SimpleCollection* subnets = (*net)->getAllSubnets();
+ if (!subnets) {
+ // Shared network without subnets it weird, but we decided to
+ // accept such configurations.
+ continue;
+ }
+
+ // For each subnet, add it to a list of regular subnets.
+ for (auto subnet = subnets->begin(); subnet != subnets->end(); ++subnet) {
+ dest->add(*subnet);
+ }
+ }
+ }
+
+ /// @brief Conducts global sanity checks
+ ///
+ /// This method is very simple now, but more sanity checks are expected
+ /// in the future.
+ ///
+ /// @param cfg - the parsed structure
+ /// @param global global Dhcp4 scope
+ /// @throw DhcpConfigError in case of issues found
+ void
+ sanityChecks(const SrvConfigPtr& cfg, const ConstElementPtr& global) {
+
+ /// Global lifetime sanity checks
+ cfg->sanityChecksLifetime("valid-lifetime");
+
+ /// Shared network sanity checks
+ const SharedNetwork4Collection* networks = cfg->getCfgSharedNetworks4()->getAll();
+ if (networks) {
+ sharedNetworksSanityChecks(*networks, global->get("shared-networks"));
+ }
+ }
+
+ /// @brief Sanity checks for shared networks
+ ///
+ /// This method verifies if there are no issues with shared networks.
+ /// @param networks pointer to shared networks being checked
+ /// @param json shared-networks element
+ /// @throw DhcpConfigError if issues are encountered
+ void
+ sharedNetworksSanityChecks(const SharedNetwork4Collection& networks,
+ ConstElementPtr json) {
+
+ /// @todo: in case of errors, use json to extract line numbers.
+ if (!json) {
+ // No json? That means that the shared-networks was never specified
+ // in the config.
+ return;
+ }
+
+ // Used for names uniqueness checks.
+ std::set<string> names;
+
+ // Let's go through all the networks one by one
+ for (auto net = networks.begin(); net != networks.end(); ++net) {
+ string txt;
+
+ // Let's check if all subnets have either the same interface
+ // or don't have the interface specified at all.
+ bool authoritative = (*net)->getAuthoritative();
+ string iface = (*net)->getIface();
+
+ const Subnet4SimpleCollection* subnets = (*net)->getAllSubnets();
+ if (subnets) {
+ // For each subnet, add it to a list of regular subnets.
+ for (auto subnet = subnets->begin(); subnet != subnets->end(); ++subnet) {
+ if ((*subnet)->getAuthoritative() != authoritative) {
+ isc_throw(DhcpConfigError, "Subnet " << boolalpha
+ << (*subnet)->toText()
+ << " has different authoritative setting "
+ << (*subnet)->getAuthoritative()
+ << " than the shared-network itself: "
+ << authoritative);
+ }
+
+ if (iface.empty()) {
+ iface = (*subnet)->getIface();
+ continue;
+ }
+
+ if ((*subnet)->getIface().empty()) {
+ continue;
+ }
+
+ if ((*subnet)->getIface() != iface) {
+ isc_throw(DhcpConfigError, "Subnet " << (*subnet)->toText()
+ << " has specified interface " << (*subnet)->getIface()
+ << ", but earlier subnet in the same shared-network"
+ << " or the shared-network itself used " << iface);
+ }
+
+ // Let's collect the subnets in case we later find out the
+ // subnet doesn't have a mandatory name.
+ txt += (*subnet)->toText() + " ";
+ }
+ }
+
+ // Next, let's check name of the shared network.
+ if ((*net)->getName().empty()) {
+ isc_throw(DhcpConfigError, "Shared-network with subnets "
+ << txt << " is missing mandatory 'name' parameter");
+ }
+
+ // Is it unique?
+ if (names.find((*net)->getName()) != names.end()) {
+ isc_throw(DhcpConfigError, "A shared-network with "
+ "name " << (*net)->getName() << " defined twice.");
+ }
+ names.insert((*net)->getName());
+
+ }
+ }
+};
+
+} // anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Initialize the command channel based on the staging configuration
+///
+/// Only close the current channel, if the new channel configuration is
+/// different. This avoids disconnecting a client and hence not sending them
+/// a command result, unless they specifically alter the channel configuration.
+/// In that case the user simply has to accept they'll be disconnected.
+///
+void configureCommandChannel() {
+ // Get new socket configuration.
+ ConstElementPtr sock_cfg =
+ CfgMgr::instance().getStagingCfg()->getControlSocketInfo();
+
+ // Get current socket configuration.
+ ConstElementPtr current_sock_cfg =
+ CfgMgr::instance().getCurrentCfg()->getControlSocketInfo();
+
+ // Determine if the socket configuration has changed. It has if
+ // both old and new configuration is specified but respective
+ // data elements aren't equal.
+ bool sock_changed = (sock_cfg && current_sock_cfg &&
+ !sock_cfg->equals(*current_sock_cfg));
+
+ // If the previous or new socket configuration doesn't exist or
+ // the new configuration differs from the old configuration we
+ // close the existing socket and open a new socket as appropriate.
+ // Note that closing an existing socket means the client will not
+ // receive the configuration result.
+ if (!sock_cfg || !current_sock_cfg || sock_changed) {
+ // Close the existing socket (if any).
+ isc::config::CommandMgr::instance().closeCommandSocket();
+
+ if (sock_cfg) {
+ // This will create a control socket and install the external
+ // socket in IfaceMgr. That socket will be monitored when
+ // Dhcp4Srv::receivePacket() calls IfaceMgr::receive4() and
+ // callback in CommandMgr will be called, if necessary.
+ isc::config::CommandMgr::instance().openCommandSocket(sock_cfg);
+ }
+ }
+}
+
+isc::data::ConstElementPtr
+configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set,
+ bool check_only) {
+ if (!config_set) {
+ ConstElementPtr answer = isc::config::createAnswer(CONTROL_RESULT_ERROR,
+ string("Can't parse NULL config"));
+ return (answer);
+ }
+
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_START)
+ .arg(server.redactConfig(config_set)->str());
+
+ // Before starting any subnet operations, let's reset the subnet-id counter,
+ // so newly recreated configuration starts with first subnet-id equal 1.
+ Subnet::resetSubnetID();
+
+ // Close DHCP sockets and remove any existing timers.
+ if (!check_only) {
+ IfaceMgr::instance().closeSockets();
+ TimerMgr::instance()->unregisterTimers();
+ server.discardPackets();
+ server.getCBControl()->reset();
+ }
+
+ // Revert any runtime option definitions configured so far and not committed.
+ LibDHCP::revertRuntimeOptionDefs();
+ // Let's set empty container in case a user hasn't specified any configuration
+ // for option definitions. This is equivalent to committing empty container.
+ LibDHCP::setRuntimeOptionDefs(OptionDefSpaceContainer());
+
+ // Print the list of known backends.
+ HostDataSourceFactory::printRegistered();
+
+ // Answer will hold the result.
+ ConstElementPtr answer;
+ // Rollback informs whether error occurred and original data
+ // have to be restored to global storages.
+ bool rollback = false;
+ // Global parameter name in case of an error.
+ string parameter_name;
+ ElementPtr mutable_cfg;
+ SrvConfigPtr srv_config;
+ try {
+ // Get the staging configuration.
+ srv_config = CfgMgr::instance().getStagingCfg();
+
+ // This is a way to convert ConstElementPtr to ElementPtr.
+ // We need a config that can be edited, because we will insert
+ // default values and will insert derived values as well.
+ mutable_cfg = boost::const_pointer_cast<Element>(config_set);
+
+ // Relocate dhcp-ddns parameters that have moved to global scope.
+ // Rule is that a global value overrides the dhcp-ddns value, so
+ // we need to do this before we apply global defaults.
+ // Note this is done for backward compatibility.
+ srv_config->moveDdnsParams(mutable_cfg);
+
+ // Move from reservation mode to new reservations flags.
+ // @todo add warning
+ BaseNetworkParser::moveReservationMode(mutable_cfg);
+
+ // Set all default values if not specified by the user.
+ SimpleParser4::setAllDefaults(mutable_cfg);
+
+ // And now derive (inherit) global parameters to subnets, if not specified.
+ SimpleParser4::deriveParameters(mutable_cfg);
+
+ // In principle we could have the following code structured as a series
+ // of long if else if clauses. That would give a marginal performance
+ // boost, but would make the code less readable. We had serious issues
+ // with the parser code debugability, so I decided to keep it as a
+ // series of independent ifs.
+
+ // This parser is used in several places.
+ Dhcp4ConfigParser global_parser;
+
+ // Apply global options in the staging config, e.g. ip-reservations-unique
+ global_parser.parseEarly(srv_config, mutable_cfg);
+
+ // We need definitions first
+ ConstElementPtr option_defs = mutable_cfg->get("option-def");
+ if (option_defs) {
+ parameter_name = "option-def";
+ OptionDefListParser parser(AF_INET);
+ CfgOptionDefPtr cfg_option_def = srv_config->getCfgOptionDef();
+ parser.parse(cfg_option_def, option_defs);
+ }
+
+ ConstElementPtr option_datas = mutable_cfg->get("option-data");
+ if (option_datas) {
+ parameter_name = "option-data";
+ OptionDataListParser parser(AF_INET);
+ CfgOptionPtr cfg_option = srv_config->getCfgOption();
+ parser.parse(cfg_option, option_datas);
+ }
+
+ ConstElementPtr control_socket = mutable_cfg->get("control-socket");
+ if (control_socket) {
+ parameter_name = "control-socket";
+ ControlSocketParser parser;
+ parser.parse(*srv_config, control_socket);
+ }
+
+ ConstElementPtr multi_threading = mutable_cfg->get("multi-threading");
+ if (multi_threading) {
+ parameter_name = "multi-threading";
+ MultiThreadingConfigParser parser;
+ parser.parse(*srv_config, multi_threading);
+ }
+
+ /// depends on "multi-threading" being enabled, so it must come after.
+ ConstElementPtr queue_control = mutable_cfg->get("dhcp-queue-control");
+ if (queue_control) {
+ parameter_name = "dhcp-queue-control";
+ DHCPQueueControlParser parser;
+ srv_config->setDHCPQueueControl(parser.parse(queue_control));
+ }
+
+ /// depends on "multi-threading" being enabled, so it must come after.
+ ConstElementPtr reservations_lookup_first = mutable_cfg->get("reservations-lookup-first");
+ if (reservations_lookup_first) {
+ parameter_name = "reservations-lookup-first";
+ if (MultiThreadingMgr::instance().getMode()) {
+ LOG_WARN(dhcp4_logger, DHCP4_RESERVATIONS_LOOKUP_FIRST_ENABLED);
+ }
+ srv_config->setReservationsLookupFirst(reservations_lookup_first->boolValue());
+ }
+
+ ConstElementPtr hr_identifiers =
+ mutable_cfg->get("host-reservation-identifiers");
+ if (hr_identifiers) {
+ parameter_name = "host-reservation-identifiers";
+ HostReservationIdsParser4 parser;
+ parser.parse(hr_identifiers);
+ }
+
+ ConstElementPtr ifaces_config = mutable_cfg->get("interfaces-config");
+ if (ifaces_config) {
+ parameter_name = "interfaces-config";
+ IfacesConfigParser parser(AF_INET, check_only);
+ CfgIfacePtr cfg_iface = srv_config->getCfgIface();
+ parser.parse(cfg_iface, ifaces_config);
+ }
+
+ ConstElementPtr sanity_checks = mutable_cfg->get("sanity-checks");
+ if (sanity_checks) {
+ parameter_name = "sanity-checks";
+ SanityChecksParser parser;
+ parser.parse(*srv_config, sanity_checks);
+ }
+
+ ConstElementPtr expiration_cfg =
+ mutable_cfg->get("expired-leases-processing");
+ if (expiration_cfg) {
+ parameter_name = "expired-leases-processing";
+ ExpirationConfigParser parser;
+ parser.parse(expiration_cfg);
+ }
+
+ // The hooks-libraries configuration must be parsed after parsing
+ // multi-threading configuration so that libraries are checked
+ // for multi-threading compatibility.
+ ConstElementPtr hooks_libraries = mutable_cfg->get("hooks-libraries");
+ if (hooks_libraries) {
+ parameter_name = "hooks-libraries";
+ HooksLibrariesParser hooks_parser;
+ HooksConfig& libraries = srv_config->getHooksConfig();
+ hooks_parser.parse(libraries, hooks_libraries);
+ libraries.verifyLibraries(hooks_libraries->getPosition());
+ }
+
+ // D2 client configuration.
+ D2ClientConfigPtr d2_client_cfg;
+
+ // Legacy DhcpConfigParser stuff below.
+ ConstElementPtr dhcp_ddns = mutable_cfg->get("dhcp-ddns");
+ if (dhcp_ddns) {
+ parameter_name = "dhcp-ddns";
+ // Apply defaults
+ D2ClientConfigParser::setAllDefaults(dhcp_ddns);
+ D2ClientConfigParser parser;
+ d2_client_cfg = parser.parse(dhcp_ddns);
+ }
+
+ ConstElementPtr client_classes = mutable_cfg->get("client-classes");
+ if (client_classes) {
+ parameter_name = "client-classes";
+ ClientClassDefListParser parser;
+ ClientClassDictionaryPtr dictionary =
+ parser.parse(client_classes, AF_INET);
+ srv_config->setClientClassDictionary(dictionary);
+ }
+
+ // Please move at the end when migration will be finished.
+ ConstElementPtr lease_database = mutable_cfg->get("lease-database");
+ if (lease_database) {
+ parameter_name = "lease-database";
+ db::DbAccessParser parser;
+ std::string access_string;
+ parser.parse(access_string, lease_database);
+ CfgDbAccessPtr cfg_db_access = srv_config->getCfgDbAccess();
+ cfg_db_access->setLeaseDbAccessString(access_string);
+ }
+
+ ConstElementPtr hosts_database = mutable_cfg->get("hosts-database");
+ if (hosts_database) {
+ parameter_name = "hosts-database";
+ db::DbAccessParser parser;
+ std::string access_string;
+ parser.parse(access_string, hosts_database);
+ CfgDbAccessPtr cfg_db_access = srv_config->getCfgDbAccess();
+ cfg_db_access->setHostDbAccessString(access_string);
+ }
+
+ ConstElementPtr hosts_databases = mutable_cfg->get("hosts-databases");
+ if (hosts_databases) {
+ parameter_name = "hosts-databases";
+ CfgDbAccessPtr cfg_db_access = srv_config->getCfgDbAccess();
+ for (auto it : hosts_databases->listValue()) {
+ db::DbAccessParser parser;
+ std::string access_string;
+ parser.parse(access_string, it);
+ cfg_db_access->setHostDbAccessString(access_string);
+ }
+ }
+
+ // Keep relative orders of shared networks and subnets.
+ ConstElementPtr shared_networks = mutable_cfg->get("shared-networks");
+ if (shared_networks) {
+ parameter_name = "shared-networks";
+ /// We need to create instance of SharedNetworks4ListParser
+ /// and parse the list of the shared networks into the
+ /// CfgSharedNetworks4 object. One additional step is then to
+ /// add subnets from the CfgSharedNetworks4 into CfgSubnets4
+ /// as well.
+ SharedNetworks4ListParser parser;
+ CfgSharedNetworks4Ptr cfg = srv_config->getCfgSharedNetworks4();
+ parser.parse(cfg, shared_networks);
+
+ // We also need to put the subnets it contains into normal
+ // subnets list.
+ global_parser.copySubnets4(srv_config->getCfgSubnets4(), cfg);
+ }
+
+ ConstElementPtr subnet4 = mutable_cfg->get("subnet4");
+ if (subnet4) {
+ parameter_name = "subnet4";
+ Subnets4ListConfigParser subnets_parser;
+ // parse() returns number of subnets parsed. We may log it one day.
+ subnets_parser.parse(srv_config, subnet4);
+ }
+
+ ConstElementPtr reservations = mutable_cfg->get("reservations");
+ if (reservations) {
+ parameter_name = "reservations";
+ HostCollection hosts;
+ HostReservationsListParser<HostReservationParser4> parser;
+ parser.parse(SUBNET_ID_GLOBAL, reservations, hosts);
+ for (auto h = hosts.begin(); h != hosts.end(); ++h) {
+ srv_config->getCfgHosts()->add(*h);
+ }
+ }
+
+ ConstElementPtr config_control = mutable_cfg->get("config-control");
+ if (config_control) {
+ parameter_name = "config-control";
+ ConfigControlParser parser;
+ ConfigControlInfoPtr config_ctl_info = parser.parse(config_control);
+ CfgMgr::instance().getStagingCfg()->setConfigControlInfo(config_ctl_info);
+ }
+
+ ConstElementPtr compatibility = mutable_cfg->get("compatibility");
+ if (compatibility) {
+ for (auto kv : compatibility->mapValue()) {
+ if (kv.first == "lenient-option-parsing") {
+ CfgMgr::instance().getStagingCfg()->setLenientOptionParsing(
+ kv.second->boolValue());
+ }
+ }
+ }
+
+ // Make parsers grouping.
+ ConfigPair config_pair;
+ const std::map<std::string, ConstElementPtr>& values_map =
+ mutable_cfg->mapValue();
+
+ BOOST_FOREACH(config_pair, values_map) {
+
+ parameter_name = config_pair.first;
+
+ // These are converted to SimpleParser and are handled already above.
+ if ((config_pair.first == "option-def") ||
+ (config_pair.first == "option-data") ||
+ (config_pair.first == "control-socket") ||
+ (config_pair.first == "multi-threading") ||
+ (config_pair.first == "dhcp-queue-control") ||
+ (config_pair.first == "host-reservation-identifiers") ||
+ (config_pair.first == "interfaces-config") ||
+ (config_pair.first == "sanity-checks") ||
+ (config_pair.first == "expired-leases-processing") ||
+ (config_pair.first == "hooks-libraries") ||
+ (config_pair.first == "dhcp-ddns") ||
+ (config_pair.first == "client-classes") ||
+ (config_pair.first == "lease-database") ||
+ (config_pair.first == "hosts-database") ||
+ (config_pair.first == "hosts-databases") ||
+ (config_pair.first == "subnet4") ||
+ (config_pair.first == "shared-networks") ||
+ (config_pair.first == "reservations") ||
+ (config_pair.first == "config-control") ||
+ (config_pair.first == "loggers") ||
+ (config_pair.first == "compatibility")) {
+ continue;
+ }
+
+ // As of Kea 1.6.0 we have two ways of inheriting the global parameters.
+ // The old method is used in JSON configuration parsers when the global
+ // parameters are derived into the subnets and shared networks and are
+ // being treated as explicitly specified. The new way used by the config
+ // backend is the dynamic inheritance whereby each subnet and shared
+ // network uses a callback function to return global parameter if it
+ // is not specified at lower level. This callback uses configured globals.
+ // We deliberately include both default and explicitly specified globals
+ // so as the callback can access the appropriate global values regardless
+ // whether they are set to a default or other value.
+ if ( (config_pair.first == "renew-timer") ||
+ (config_pair.first == "rebind-timer") ||
+ (config_pair.first == "valid-lifetime") ||
+ (config_pair.first == "min-valid-lifetime") ||
+ (config_pair.first == "max-valid-lifetime") ||
+ (config_pair.first == "decline-probation-period") ||
+ (config_pair.first == "dhcp4o6-port") ||
+ (config_pair.first == "echo-client-id") ||
+ (config_pair.first == "match-client-id") ||
+ (config_pair.first == "authoritative") ||
+ (config_pair.first == "next-server") ||
+ (config_pair.first == "server-hostname") ||
+ (config_pair.first == "boot-file-name") ||
+ (config_pair.first == "server-tag") ||
+ (config_pair.first == "reservation-mode") ||
+ (config_pair.first == "reservations-global") ||
+ (config_pair.first == "reservations-in-subnet") ||
+ (config_pair.first == "reservations-out-of-pool") ||
+ (config_pair.first == "calculate-tee-times") ||
+ (config_pair.first == "t1-percent") ||
+ (config_pair.first == "t2-percent") ||
+ (config_pair.first == "cache-threshold") ||
+ (config_pair.first == "cache-max-age") ||
+ (config_pair.first == "hostname-char-set") ||
+ (config_pair.first == "hostname-char-replacement") ||
+ (config_pair.first == "ddns-send-updates") ||
+ (config_pair.first == "ddns-override-no-update") ||
+ (config_pair.first == "ddns-override-client-update") ||
+ (config_pair.first == "ddns-replace-client-name") ||
+ (config_pair.first == "ddns-generated-prefix") ||
+ (config_pair.first == "ddns-qualifying-suffix") ||
+ (config_pair.first == "ddns-update-on-renew") ||
+ (config_pair.first == "ddns-use-conflict-resolution") ||
+ (config_pair.first == "store-extended-info") ||
+ (config_pair.first == "statistic-default-sample-count") ||
+ (config_pair.first == "statistic-default-sample-age") ||
+ (config_pair.first == "early-global-reservations-lookup") ||
+ (config_pair.first == "ip-reservations-unique") ||
+ (config_pair.first == "reservations-lookup-first") ||
+ (config_pair.first == "parked-packet-limit")) {
+ CfgMgr::instance().getStagingCfg()->addConfiguredGlobal(config_pair.first,
+ config_pair.second);
+ continue;
+ }
+
+ // Nothing to configure for the user-context.
+ if (config_pair.first == "user-context") {
+ continue;
+ }
+
+ // If we got here, no code handled this parameter, so we bail out.
+ isc_throw(DhcpConfigError,
+ "unsupported global configuration parameter: " << config_pair.first
+ << " (" << config_pair.second->getPosition() << ")");
+ }
+
+ // Reset parameter name.
+ parameter_name = "<post parsing>";
+
+ // Apply global options in the staging config.
+ global_parser.parse(srv_config, mutable_cfg);
+
+ // This method conducts final sanity checks and tweaks. In particular,
+ // it checks that there is no conflict between plain subnets and those
+ // defined as part of shared networks.
+ global_parser.sanityChecks(srv_config, mutable_cfg);
+
+ // Validate D2 client configuration.
+ if (!d2_client_cfg) {
+ d2_client_cfg.reset(new D2ClientConfig());
+ }
+ d2_client_cfg->validateContents();
+ srv_config->setD2ClientConfig(d2_client_cfg);
+ } catch (const isc::Exception& ex) {
+ LOG_ERROR(dhcp4_logger, DHCP4_PARSER_FAIL)
+ .arg(parameter_name).arg(ex.what());
+ answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, ex.what());
+
+ // An error occurred, so make sure that we restore original data.
+ rollback = true;
+ } catch (...) {
+ // For things like bad_cast in boost::lexical_cast
+ LOG_ERROR(dhcp4_logger, DHCP4_PARSER_EXCEPTION).arg(parameter_name);
+ answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, "undefined configuration"
+ " processing error");
+
+ // An error occurred, so make sure that we restore original data.
+ rollback = true;
+ }
+
+ if (check_only) {
+ rollback = true;
+ if (!answer) {
+ answer = isc::config::createAnswer(CONTROL_RESULT_SUCCESS,
+ "Configuration seems sane. Control-socket, hook-libraries, and D2 "
+ "configuration were sanity checked, but not applied.");
+ }
+ }
+
+ // So far so good, there was no parsing error so let's commit the
+ // configuration. This will add created subnets and option values into
+ // the server's configuration.
+ // This operation should be exception safe but let's make sure.
+ if (!rollback) {
+ try {
+
+ // Setup the command channel.
+ configureCommandChannel();
+
+ // No need to commit interface names as this is handled by the
+ // CfgMgr::commit() function.
+
+ // Apply the staged D2ClientConfig, used to be done by parser commit
+ D2ClientConfigPtr cfg;
+ cfg = CfgMgr::instance().getStagingCfg()->getD2ClientConfig();
+ CfgMgr::instance().setD2ClientConfig(cfg);
+
+ // This occurs last as if it succeeds, there is no easy way to
+ // revert it. As a result, the failure to commit a subsequent
+ // change causes problems when trying to roll back.
+ HooksManager::prepareUnloadLibraries();
+ static_cast<void>(HooksManager::unloadLibraries());
+ const HooksConfig& libraries =
+ CfgMgr::instance().getStagingCfg()->getHooksConfig();
+ libraries.loadLibraries();
+ } catch (const isc::Exception& ex) {
+ LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what());
+ answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, ex.what());
+
+ // An error occurred, so make sure to restore the original data.
+ rollback = true;
+ } catch (...) {
+ // For things like bad_cast in boost::lexical_cast
+ LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_EXCEPTION);
+ answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, "undefined configuration"
+ " parsing error");
+
+ // An error occurred, so make sure to restore the original data.
+ rollback = true;
+ }
+ }
+
+ // Moved from the commit block to add the config backend indication.
+ if (!rollback) {
+ try {
+
+ // If there are config backends, fetch and merge into staging config
+ server.getCBControl()->databaseConfigFetch(srv_config,
+ CBControlDHCPv4::FetchMode::FETCH_ALL);
+ } catch (const isc::Exception& ex) {
+ std::ostringstream err;
+ err << "during update from config backend database: " << ex.what();
+ LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(err.str());
+ answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str());
+
+ // An error occurred, so make sure to restore the original data.
+ rollback = true;
+ } catch (...) {
+ // For things like bad_cast in boost::lexical_cast
+ std::ostringstream err;
+ err << "during update from config backend database: "
+ << "undefined configuration parsing error";
+ LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(err.str());
+ answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str());
+
+ // An error occurred, so make sure to restore the original data.
+ rollback = true;
+ }
+ }
+
+ // Rollback changes as the configuration parsing failed.
+ if (rollback) {
+ // Revert to original configuration of runtime option definitions
+ // in the libdhcp++.
+ LibDHCP::revertRuntimeOptionDefs();
+ return (answer);
+ }
+
+ LOG_INFO(dhcp4_logger, DHCP4_CONFIG_COMPLETE)
+ .arg(CfgMgr::instance().getStagingCfg()->
+ getConfigSummary(SrvConfig::CFGSEL_ALL4));
+
+ // Everything was fine. Configuration is successful.
+ answer = isc::config::createAnswer(CONTROL_RESULT_SUCCESS, "Configuration successful.");
+ return (answer);
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/bin/dhcp4/json_config_parser.h b/src/bin/dhcp4/json_config_parser.h
new file mode 100644
index 0000000..34b1419
--- /dev/null
+++ b/src/bin/dhcp4/json_config_parser.h
@@ -0,0 +1,66 @@
+// Copyright (C) 2012-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/.
+
+#ifndef DHCP4_CONFIG_PARSER_H
+#define DHCP4_CONFIG_PARSER_H
+
+#include <cc/data.h>
+#include <cc/stamped_value.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <exceptions/exceptions.h>
+
+#include <stdint.h>
+#include <string>
+
+/// @todo: This header file and its .cc counterpart are very similar between
+/// DHCPv4 and DHCPv6. They should be merged. A ticket #2355.
+
+namespace isc {
+namespace dhcp {
+
+class Dhcpv4Srv;
+
+/// @brief Configure DHCPv4 server (@c Dhcpv4Srv) with a set of configuration
+/// values.
+///
+/// This function parses configuration information stored in @c config_set
+/// and configures the @c server by applying the configuration to it.
+/// It provides the strong exception guarantee as long as the underlying
+/// derived class implementations of @c DhcpConfigParser meet the assumption,
+/// that is, it ensures that either configuration is fully applied or the
+/// state of the server is intact.
+///
+/// If a syntax or semantics level error happens during the configuration
+/// (such as malformed configuration or invalid configuration parameter),
+/// this function returns appropriate error code.
+///
+/// This function is called every time a new configuration is received. The
+/// extra parameter is a reference to DHCPv4 server component. It is currently
+/// not used and CfgMgr::instance() is accessed instead.
+///
+/// Test-only mode added. If check_only flag is set to true, the configuration
+/// is parsed, but the actual change is not applied. The goal is to have
+/// the ability to test configuration.
+///
+/// This method does not throw. It catches all exceptions and returns them as
+/// reconfiguration statuses. It may return the following response codes:
+/// 0 - configuration successful
+/// 1 - malformed configuration (parsing failed)
+/// 2 - commit failed (parsing was successful, but failed to store the
+/// values in to server's configuration)
+///
+/// @param server the server object
+/// @param config_set a new configuration (JSON) for DHCPv4 server
+/// @param check_only whether this configuration is for testing only
+/// @return answer that contains result of reconfiguration
+isc::data::ConstElementPtr
+configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set,
+ bool check_only = false);
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // DHCP4_CONFIG_PARSER_H
diff --git a/src/bin/dhcp4/location.hh b/src/bin/dhcp4/location.hh
new file mode 100644
index 0000000..860fbfa
--- /dev/null
+++ b/src/bin/dhcp4/location.hh
@@ -0,0 +1,306 @@
+// A Bison parser, made by GNU Bison 3.8.2.
+
+// Locations for Bison parsers in C++
+
+// Copyright (C) 2002-2015, 2018-2021 Free Software Foundation, Inc.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// As a special exception, you may create a larger work that contains
+// part or all of the Bison parser skeleton and distribute that work
+// under terms of your choice, so long as that work isn't itself a
+// parser generator using the skeleton or a modified version thereof
+// as a parser skeleton. Alternatively, if you modify or redistribute
+// the parser skeleton itself, you may (at your option) remove this
+// special exception, which will cause the skeleton and the resulting
+// Bison output files to be licensed under the GNU General Public
+// License without this special exception.
+
+// This special exception was added by the Free Software Foundation in
+// version 2.2 of Bison.
+
+/**
+ ** \file location.hh
+ ** Define the isc::dhcp::location class.
+ */
+
+#ifndef YY_PARSER4_LOCATION_HH_INCLUDED
+# define YY_PARSER4_LOCATION_HH_INCLUDED
+
+# include <iostream>
+# include <string>
+
+# ifndef YY_NULLPTR
+# if defined __cplusplus
+# if 201103L <= __cplusplus
+# define YY_NULLPTR nullptr
+# else
+# define YY_NULLPTR 0
+# endif
+# else
+# define YY_NULLPTR ((void*)0)
+# endif
+# endif
+
+#line 14 "dhcp4_parser.yy"
+namespace isc { namespace dhcp {
+#line 59 "location.hh"
+
+ /// A point in a source file.
+ class position
+ {
+ public:
+ /// Type for file name.
+ typedef const std::string filename_type;
+ /// Type for line and column numbers.
+ typedef int counter_type;
+
+ /// Construct a position.
+ explicit position (filename_type* f = YY_NULLPTR,
+ counter_type l = 1,
+ counter_type c = 1)
+ : filename (f)
+ , line (l)
+ , column (c)
+ {}
+
+
+ /// Initialization.
+ void initialize (filename_type* fn = YY_NULLPTR,
+ counter_type l = 1,
+ counter_type c = 1)
+ {
+ filename = fn;
+ line = l;
+ column = c;
+ }
+
+ /** \name Line and Column related manipulators
+ ** \{ */
+ /// (line related) Advance to the COUNT next lines.
+ void lines (counter_type count = 1)
+ {
+ if (count)
+ {
+ column = 1;
+ line = add_ (line, count, 1);
+ }
+ }
+
+ /// (column related) Advance to the COUNT next columns.
+ void columns (counter_type count = 1)
+ {
+ column = add_ (column, count, 1);
+ }
+ /** \} */
+
+ /// File name to which this position refers.
+ filename_type* filename;
+ /// Current line number.
+ counter_type line;
+ /// Current column number.
+ counter_type column;
+
+ private:
+ /// Compute max (min, lhs+rhs).
+ static counter_type add_ (counter_type lhs, counter_type rhs, counter_type min)
+ {
+ return lhs + rhs < min ? min : lhs + rhs;
+ }
+ };
+
+ /// Add \a width columns, in place.
+ inline position&
+ operator+= (position& res, position::counter_type width)
+ {
+ res.columns (width);
+ return res;
+ }
+
+ /// Add \a width columns.
+ inline position
+ operator+ (position res, position::counter_type width)
+ {
+ return res += width;
+ }
+
+ /// Subtract \a width columns, in place.
+ inline position&
+ operator-= (position& res, position::counter_type width)
+ {
+ return res += -width;
+ }
+
+ /// Subtract \a width columns.
+ inline position
+ operator- (position res, position::counter_type width)
+ {
+ return res -= width;
+ }
+
+ /** \brief Intercept output stream redirection.
+ ** \param ostr the destination output stream
+ ** \param pos a reference to the position to redirect
+ */
+ template <typename YYChar>
+ std::basic_ostream<YYChar>&
+ operator<< (std::basic_ostream<YYChar>& ostr, const position& pos)
+ {
+ if (pos.filename)
+ ostr << *pos.filename << ':';
+ return ostr << pos.line << '.' << pos.column;
+ }
+
+ /// Two points in a source file.
+ class location
+ {
+ public:
+ /// Type for file name.
+ typedef position::filename_type filename_type;
+ /// Type for line and column numbers.
+ typedef position::counter_type counter_type;
+
+ /// Construct a location from \a b to \a e.
+ location (const position& b, const position& e)
+ : begin (b)
+ , end (e)
+ {}
+
+ /// Construct a 0-width location in \a p.
+ explicit location (const position& p = position ())
+ : begin (p)
+ , end (p)
+ {}
+
+ /// Construct a 0-width location in \a f, \a l, \a c.
+ explicit location (filename_type* f,
+ counter_type l = 1,
+ counter_type c = 1)
+ : begin (f, l, c)
+ , end (f, l, c)
+ {}
+
+
+ /// Initialization.
+ void initialize (filename_type* f = YY_NULLPTR,
+ counter_type l = 1,
+ counter_type c = 1)
+ {
+ begin.initialize (f, l, c);
+ end = begin;
+ }
+
+ /** \name Line and Column related manipulators
+ ** \{ */
+ public:
+ /// Reset initial location to final location.
+ void step ()
+ {
+ begin = end;
+ }
+
+ /// Extend the current location to the COUNT next columns.
+ void columns (counter_type count = 1)
+ {
+ end += count;
+ }
+
+ /// Extend the current location to the COUNT next lines.
+ void lines (counter_type count = 1)
+ {
+ end.lines (count);
+ }
+ /** \} */
+
+
+ public:
+ /// Beginning of the located region.
+ position begin;
+ /// End of the located region.
+ position end;
+ };
+
+ /// Join two locations, in place.
+ inline location&
+ operator+= (location& res, const location& end)
+ {
+ res.end = end.end;
+ return res;
+ }
+
+ /// Join two locations.
+ inline location
+ operator+ (location res, const location& end)
+ {
+ return res += end;
+ }
+
+ /// Add \a width columns to the end position, in place.
+ inline location&
+ operator+= (location& res, location::counter_type width)
+ {
+ res.columns (width);
+ return res;
+ }
+
+ /// Add \a width columns to the end position.
+ inline location
+ operator+ (location res, location::counter_type width)
+ {
+ return res += width;
+ }
+
+ /// Subtract \a width columns to the end position, in place.
+ inline location&
+ operator-= (location& res, location::counter_type width)
+ {
+ return res += -width;
+ }
+
+ /// Subtract \a width columns to the end position.
+ inline location
+ operator- (location res, location::counter_type width)
+ {
+ return res -= width;
+ }
+
+ /** \brief Intercept output stream redirection.
+ ** \param ostr the destination output stream
+ ** \param loc a reference to the location to redirect
+ **
+ ** Avoid duplicate information.
+ */
+ template <typename YYChar>
+ std::basic_ostream<YYChar>&
+ operator<< (std::basic_ostream<YYChar>& ostr, const location& loc)
+ {
+ location::counter_type end_col
+ = 0 < loc.end.column ? loc.end.column - 1 : 0;
+ ostr << loc.begin;
+ if (loc.end.filename
+ && (!loc.begin.filename
+ || *loc.begin.filename != *loc.end.filename))
+ ostr << '-' << loc.end.filename << ':' << loc.end.line << '.' << end_col;
+ else if (loc.begin.line < loc.end.line)
+ ostr << '-' << loc.end.line << '.' << end_col;
+ else if (loc.begin.column < end_col)
+ ostr << '-' << end_col;
+ return ostr;
+ }
+
+#line 14 "dhcp4_parser.yy"
+} } // isc::dhcp
+#line 305 "location.hh"
+
+#endif // !YY_PARSER4_LOCATION_HH_INCLUDED
diff --git a/src/bin/dhcp4/main.cc b/src/bin/dhcp4/main.cc
new file mode 100644
index 0000000..219fb53
--- /dev/null
+++ b/src/bin/dhcp4/main.cc
@@ -0,0 +1,302 @@
+// Copyright (C) 2011-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 <kea_version.h>
+
+#include <cfgrpt/config_report.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/dhcp4_log.h>
+#include <dhcp4/parser_context.h>
+#include <dhcp4/json_config_parser.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <exceptions/exceptions.h>
+#include <log/logger_support.h>
+#include <log/logger_manager.h>
+#include <process/daemon.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <iostream>
+
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::process;
+using namespace std;
+
+/// This file contains entry point (main() function) for standard DHCPv4 server
+/// component of Kea software suite. It parses command-line arguments and
+/// instantiates ControlledDhcpv4Srv class that is responsible for establishing
+/// connection with msgq (receiving commands and configuration) and also
+/// creating Dhcpv4 server object as well.
+///
+/// For detailed explanation or relations between main(), ControlledDhcpv4Srv,
+/// Dhcpv4Srv and other classes, see \ref dhcpv4Session.
+
+namespace {
+
+const char* const DHCP4_NAME = "kea-dhcp4";
+
+/// @brief Prints Kea Usage and exits
+///
+/// Note: This function never returns. It terminates the process.
+void
+usage() {
+ cerr << "Kea DHCPv4 server, "
+ << "version " << VERSION
+ << " (" << PACKAGE_VERSION_TYPE << ")"
+ << endl;
+ cerr << endl;
+ cerr << "Usage: " << DHCP4_NAME
+ << " -[v|V|W] [-d] [-{c|t} cfgfile] [-p number] [-P number]" << endl;
+ cerr << " -v: print version number and exit" << endl;
+ cerr << " -V: print extended version and exit" << endl;
+ cerr << " -W: display the configuration report and exit" << endl;
+ cerr << " -d: debug mode with extra verbosity (former -v)" << endl;
+ cerr << " -c file: specify configuration file" << endl;
+ cerr << " -t file: check the configuration file syntax and exit" << endl;
+ cerr << " -p number: specify non-standard server port number 1-65535 "
+ << "(useful for testing only)" << endl;
+ cerr << " -P number: specify non-standard client port number 1-65535 "
+ << "(useful for testing only)" << endl;
+ exit(EXIT_FAILURE);
+}
+} // namespace
+
+int
+main(int argc, char* argv[]) {
+ int ch;
+ // The default. Any other values are useful for testing only.
+ int server_port_number = DHCP4_SERVER_PORT;
+ // Not zero values are useful for testing only.
+ int client_port_number = 0;
+ bool verbose_mode = false; // Should server be verbose?
+ bool check_mode = false; // Check syntax
+
+ // The standard config file
+ std::string config_file("");
+
+ while ((ch = getopt(argc, argv, "dvVWc:p:P:t:")) != -1) {
+ switch (ch) {
+ case 'd':
+ verbose_mode = true;
+ break;
+
+ case 'v':
+ cout << Dhcpv4Srv::getVersion(false) << endl;
+ return (EXIT_SUCCESS);
+
+ case 'V':
+ cout << Dhcpv4Srv::getVersion(true) << endl;
+ return (EXIT_SUCCESS);
+
+ case 'W':
+ cout << isc::detail::getConfigReport() << endl;
+ return (EXIT_SUCCESS);
+
+ case 't':
+ check_mode = true;
+ // falls through
+
+ case 'c': // config file
+ config_file = optarg;
+ break;
+
+ case 'p': // server port number
+ try {
+ server_port_number = boost::lexical_cast<int>(optarg);
+ } catch (const boost::bad_lexical_cast &) {
+ cerr << "Failed to parse server port number: [" << optarg
+ << "], 1-65535 allowed." << endl;
+ usage();
+ }
+ if (server_port_number <= 0 || server_port_number > 65535) {
+ cerr << "Failed to parse server port number: [" << optarg
+ << "], 1-65535 allowed." << endl;
+ usage();
+ }
+ break;
+
+ case 'P': // client port number
+ try {
+ client_port_number = boost::lexical_cast<int>(optarg);
+ } catch (const boost::bad_lexical_cast &) {
+ cerr << "Failed to parse client port number: [" << optarg
+ << "], 1-65535 allowed." << endl;
+ usage();
+ }
+ if (client_port_number <= 0 || client_port_number > 65535) {
+ cerr << "Failed to parse client port number: [" << optarg
+ << "], 1-65535 allowed." << endl;
+ usage();
+ }
+ break;
+
+ default:
+ usage();
+ }
+ }
+
+ // Check for extraneous parameters.
+ if (argc > optind) {
+ usage();
+ }
+
+ // Configuration file is required.
+ if (config_file.empty()) {
+ cerr << "Configuration file not specified." << endl;
+ usage();
+ }
+
+ // This is the DHCPv4 server
+ CfgMgr::instance().setFamily(AF_INET);
+
+ if (check_mode) {
+ try {
+ // We need to initialize logging, in case any error messages are to be printed.
+ // This is just a test, so we don't care about lockfile.
+ setenv("KEA_LOCKFILE_DIR", "none", 0);
+ Daemon::setDefaultLoggerName(DHCP4_ROOT_LOGGER_NAME);
+ Daemon::loggerInit(DHCP4_ROOT_LOGGER_NAME, verbose_mode);
+
+ // Check the syntax first.
+ Parser4Context parser;
+ ConstElementPtr json;
+ json = parser.parseFile(config_file, Parser4Context::PARSER_DHCP4);
+ if (!json) {
+ cerr << "No configuration found" << endl;
+ return (EXIT_FAILURE);
+ }
+ if (verbose_mode) {
+ cerr << "Syntax check OK" << endl;
+ }
+
+ // Check the logic next.
+ ConstElementPtr dhcp4 = json->get("Dhcp4");
+ if (!dhcp4) {
+ cerr << "Missing mandatory Dhcp4 element" << endl;
+ return (EXIT_FAILURE);
+ }
+ ControlledDhcpv4Srv server(0);
+ ConstElementPtr answer;
+
+ // Now we pass the Dhcp4 configuration to the server, but
+ // tell it to check the configuration only (check_only = true)
+ answer = configureDhcp4Server(server, dhcp4, true);
+
+ int status_code = 0;
+ answer = isc::config::parseAnswer(status_code, answer);
+ if (status_code == 0) {
+ return (EXIT_SUCCESS);
+ } else {
+ cerr << "Error encountered: " << answer->stringValue() << endl;
+ return (EXIT_FAILURE);
+ }
+ } catch (const std::exception& ex) {
+ cerr << "Syntax check failed with: " << ex.what() << endl;
+ }
+ return (EXIT_FAILURE);
+ }
+
+ int ret = EXIT_SUCCESS;
+ try {
+ // It is important that we set a default logger name because this name
+ // will be used when the user doesn't provide the logging configuration
+ // in the Kea configuration file.
+ Daemon::setDefaultLoggerName(DHCP4_ROOT_LOGGER_NAME);
+
+ // Initialize logging. If verbose, we'll use maximum verbosity.
+ Daemon::loggerInit(DHCP4_ROOT_LOGGER_NAME, verbose_mode);
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO)
+ .arg(getpid())
+ .arg(server_port_number)
+ .arg(client_port_number)
+ .arg(verbose_mode ? "yes" : "no");
+
+ LOG_INFO(dhcp4_logger, DHCP4_STARTING)
+ .arg(VERSION)
+ .arg(PACKAGE_VERSION_TYPE);
+
+ if (string(PACKAGE_VERSION_TYPE) == "development") {
+ LOG_WARN(dhcp4_logger, DHCP4_DEVELOPMENT_VERSION);
+ }
+
+ // Create the server instance.
+ ControlledDhcpv4Srv server(server_port_number, client_port_number);
+
+ // Remember verbose-mode
+ server.setVerbose(verbose_mode);
+
+ // Create our PID file.
+ server.setProcName(DHCP4_NAME);
+ server.setConfigFile(config_file);
+ server.createPIDFile();
+
+ try {
+ // Initialize the server.
+ server.init(config_file);
+ } catch (const std::exception& ex) {
+
+ try {
+ // Let's log out what went wrong.
+ isc::log::LoggerManager log_manager;
+ log_manager.process();
+ LOG_ERROR(dhcp4_logger, DHCP4_INIT_FAIL).arg(ex.what());
+ } catch (...) {
+ // The exception thrown during the initialization could
+ // originate from logger subsystem. Therefore LOG_ERROR()
+ // may fail as well.
+ cerr << "Failed to initialize server: " << ex.what() << endl;
+ }
+
+ return (EXIT_FAILURE);
+ }
+
+ // Tell the admin we are ready to process packets
+ LOG_INFO(dhcp4_logger, DHCP4_STARTED).arg(VERSION);
+
+ // And run the main loop of the server.
+ ret = server.run();
+
+ LOG_INFO(dhcp4_logger, DHCP4_SHUTDOWN);
+
+ } catch (const isc::process::DaemonPIDExists& ex) {
+ // First, we print the error on stderr (that should always work)
+ cerr << DHCP4_NAME << " already running? " << ex.what()
+ << endl;
+
+ // Let's also try to log it using logging system, but we're not
+ // sure if it's usable (the exception may have been thrown from
+ // the logger subsystem)
+ try {
+ LOG_FATAL(dhcp4_logger, DHCP4_ALREADY_RUNNING)
+ .arg(DHCP4_NAME).arg(ex.what());
+ } catch (...) {
+ // Already logged so ignore
+ }
+ ret = EXIT_FAILURE;
+ } catch (const std::exception& ex) {
+ // First, we print the error on stderr (that should always work)
+ cerr << DHCP4_NAME << ": Fatal error during start up: " << ex.what()
+ << endl;
+
+ // Let's also try to log it using logging system, but we're not
+ // sure if it's usable (the exception may have been thrown from
+ // the logger subsystem)
+ try {
+ LOG_FATAL(dhcp4_logger, DHCP4_SERVER_FAILED).arg(ex.what());
+ } catch (...) {
+ // Already logged so ignore
+ }
+ ret = EXIT_FAILURE;
+ } catch (...) {
+ cerr << DHCP4_NAME << ": Fatal error during start up"
+ << endl;
+ ret = EXIT_FAILURE;
+ }
+
+ return (ret);
+}
diff --git a/src/bin/dhcp4/parser_context.cc b/src/bin/dhcp4/parser_context.cc
new file mode 100644
index 0000000..55902e7
--- /dev/null
+++ b/src/bin/dhcp4/parser_context.cc
@@ -0,0 +1,244 @@
+// Copyright (C) 2016-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 <dhcp4/parser_context.h>
+#include <dhcp4/dhcp4_parser.h>
+#include <dhcp4/dhcp4_log.h>
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <boost/lexical_cast.hpp>
+#include <fstream>
+#include <sstream>
+#include <limits>
+
+namespace isc {
+namespace dhcp {
+
+Parser4Context::Parser4Context()
+ : sfile_(nullptr), ctx_(NO_KEYWORD), trace_scanning_(false),
+ trace_parsing_(false) {
+}
+
+Parser4Context::~Parser4Context() {
+}
+
+isc::data::ElementPtr
+Parser4Context::parseString(const std::string& str, ParserType parser_type) {
+ scanStringBegin(str, parser_type);
+ return (parseCommon());
+}
+
+isc::data::ElementPtr
+Parser4Context::parseFile(const std::string& filename, ParserType parser_type) {
+ FILE* f = fopen(filename.c_str(), "r");
+ if (!f) {
+ isc_throw(Dhcp4ParseError, "Unable to open file " << filename);
+ }
+ scanFileBegin(f, filename, parser_type);
+ return (parseCommon());
+}
+
+isc::data::ElementPtr
+Parser4Context::parseCommon() {
+ isc::dhcp::Dhcp4Parser parser(*this);
+ // Uncomment this to get detailed parser logs.
+ // trace_parsing_ = true;
+ parser.set_debug_level(trace_parsing_);
+ try {
+ int res = parser.parse();
+ if (res != 0) {
+ isc_throw(Dhcp4ParseError, "Parser abort");
+ }
+ scanEnd();
+ }
+ catch (...) {
+ scanEnd();
+ throw;
+ }
+ if (stack_.size() == 1) {
+ return (stack_[0]);
+ } else {
+ isc_throw(Dhcp4ParseError, "Expected exactly one terminal Element expected, found "
+ << stack_.size());
+ }
+}
+
+void
+Parser4Context::error(const isc::dhcp::location& loc,
+ const std::string& what,
+ size_t pos) {
+ if (pos == 0) {
+ isc_throw(Dhcp4ParseError, loc << ": " << what);
+ } else {
+ isc_throw(Dhcp4ParseError, loc << " (near " << pos << "): " << what);
+ }
+}
+
+void
+Parser4Context::error(const std::string& what) {
+ isc_throw(Dhcp4ParseError, what);
+}
+
+void
+Parser4Context::fatal(const std::string& what) {
+ isc_throw(Dhcp4ParseError, what);
+}
+
+isc::data::Element::Position
+Parser4Context::loc2pos(isc::dhcp::location& loc) {
+ const std::string& file = *loc.begin.filename;
+ const uint32_t line = loc.begin.line;
+ const uint32_t pos = loc.begin.column;
+ return (isc::data::Element::Position(file, line, pos));
+}
+
+void
+Parser4Context::require(const std::string& name,
+ isc::data::Element::Position open_loc,
+ isc::data::Element::Position close_loc) {
+ ConstElementPtr value = stack_.back()->get(name);
+ if (!value) {
+ isc_throw(Dhcp4ParseError,
+ "missing parameter '" << name << "' ("
+ << stack_.back()->getPosition() << ") ["
+ << contextName() << " map between "
+ << open_loc << " and " << close_loc << "]");
+ }
+}
+
+void
+Parser4Context::unique(const std::string& name,
+ isc::data::Element::Position loc) {
+ ConstElementPtr value = stack_.back()->get(name);
+ if (value) {
+ if (ctx_ != NO_KEYWORD) {
+ isc_throw(Dhcp4ParseError, loc << ": duplicate " << name
+ << " entries in " << contextName()
+ << " map (previous at " << value->getPosition() << ")");
+ } else {
+ isc_throw(Dhcp4ParseError, loc << ": duplicate " << name
+ << " entries in JSON"
+ << " map (previous at " << value->getPosition() << ")");
+ }
+
+ }
+}
+
+void
+Parser4Context::enter(const ParserContext& ctx) {
+ cstack_.push_back(ctx_);
+ ctx_ = ctx;
+}
+
+void
+Parser4Context::leave() {
+#if 1
+ if (cstack_.empty()) {
+ fatal("unbalanced syntactic context");
+ }
+#endif
+ ctx_ = cstack_.back();
+ cstack_.pop_back();
+}
+
+const std::string
+Parser4Context::contextName() {
+ switch (ctx_) {
+ case NO_KEYWORD:
+ return ("__no keyword__");
+ case CONFIG:
+ return ("toplevel");
+ case DHCP4:
+ return ("Dhcp4");
+ case INTERFACES_CONFIG:
+ return ("interfaces-config");
+ case DHCP_SOCKET_TYPE:
+ return ("dhcp-socket-type");
+ case OUTBOUND_INTERFACE:
+ return ("outbound-interface");
+ case LEASE_DATABASE:
+ return ("lease-database");
+ case HOSTS_DATABASE:
+ return ("hosts-database");
+ case DATABASE_TYPE:
+ return ("database-type");
+ case DATABASE_ON_FAIL:
+ return ("database-on-fail");
+ case HOST_RESERVATION_IDENTIFIERS:
+ return ("host-reservation-identifiers");
+ case HOOKS_LIBRARIES:
+ return ("hooks-libraries");
+ case SUBNET4:
+ return ("subnet4");
+ case RESERVATION_MODE:
+ return ("reservation-mode");
+ case OPTION_DEF:
+ return ("option-def");
+ case OPTION_DATA:
+ return ("option-data");
+ case CLIENT_CLASSES:
+ return ("client-classes");
+ case EXPIRED_LEASES_PROCESSING:
+ return ("expired-leases-processing");
+ case SERVER_ID:
+ return ("server-id");
+ case CONTROL_SOCKET:
+ return ("control-socket");
+ case DHCP_QUEUE_CONTROL:
+ return ("dhcp-queue-control");
+ case DHCP_MULTI_THREADING:
+ return ("multi-threading");
+ case POOLS:
+ return ("pools");
+ case RESERVATIONS:
+ return ("reservations");
+ case RELAY:
+ return ("relay");
+ case LOGGERS:
+ return ("loggers");
+ case OUTPUT_OPTIONS:
+ return ("output-options");
+ case DHCP_DDNS:
+ return ("dhcp-ddns");
+ case NCR_PROTOCOL:
+ return ("ncr-protocol");
+ case NCR_FORMAT:
+ return ("ncr-format");
+ case REPLACE_CLIENT_NAME:
+ return ("replace-client-name");
+ case SHARED_NETWORK:
+ return ("shared-networks");
+ case SANITY_CHECKS:
+ return ("sanity-checks");
+ case CONFIG_CONTROL:
+ return ("config-control");
+ case CONFIG_DATABASE:
+ return ("config-database");
+ case COMPATIBILITY:
+ return ("compatibility");
+ default:
+ return ("__unknown__");
+ }
+}
+
+void
+Parser4Context::warning(const isc::dhcp::location& loc,
+ const std::string& what) {
+ std::ostringstream msg;
+ msg << loc << ": " << what;
+ LOG_WARN(dhcp4_logger, DHCP4_CONFIG_SYNTAX_WARNING)
+ .arg(msg.str());
+}
+
+void
+Parser4Context::warnAboutExtraCommas(const isc::dhcp::location& loc) {
+ warning(loc, "Extraneous comma. A piece of configuration may have been omitted.");
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/bin/dhcp4/parser_context.h b/src/bin/dhcp4/parser_context.h
new file mode 100644
index 0000000..a65ad3a
--- /dev/null
+++ b/src/bin/dhcp4/parser_context.h
@@ -0,0 +1,417 @@
+// Copyright (C) 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 PARSER_CONTEXT_H
+#define PARSER_CONTEXT_H
+#include <string>
+#include <map>
+#include <vector>
+#include <dhcp4/dhcp4_parser.h>
+#include <dhcp4/parser_context_decl.h>
+#include <exceptions/exceptions.h>
+
+// Tell Flex the lexer's prototype ...
+#define YY_DECL isc::dhcp::Dhcp4Parser::symbol_type parser4_lex (Parser4Context& driver)
+
+// ... and declare it for the parser's sake.
+YY_DECL;
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Evaluation error exception raised when trying to parse.
+///
+/// @todo: This probably should be common for Dhcp4 and Dhcp6.
+class Dhcp4ParseError : public isc::Exception {
+public:
+ Dhcp4ParseError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Evaluation context, an interface to the expression evaluation.
+class Parser4Context
+{
+public:
+
+ /// @brief Defines currently supported scopes
+ ///
+ /// Dhcp4Parser is able to parse several types of scope. Usually,
+ /// when it parses a config file, it expects the data to have a map
+ /// with Dhcp4 in it and all the parameters within that Dhcp4 map.
+ /// However, sometimes the parser is expected to parse only a subset
+ /// of that information. For example, it may be asked to parse
+ /// a structure that is host-reservation only, without the global
+ /// 'Dhcp4' or 'reservations' around it. In such case the parser
+ /// is being told to start parsing as PARSER_HOST_RESERVATION4.
+ typedef enum {
+ /// This parser will parse the content as generic JSON.
+ PARSER_JSON,
+
+ /// This parser will parse the content as Dhcp4 config wrapped in a map
+ /// (that's the regular config file)
+ PARSER_DHCP4,
+
+ /// This parser will parse the content of Dhcp4 (without outer { } and
+ /// without "Dhcp4"). It is mostly used in unit-tests as most of the
+ /// unit-tests do not define the outer map and Dhcp4 entity, just the
+ /// contents of it.
+ SUBPARSER_DHCP4,
+
+ /// This will parse the input as interfaces content.
+ PARSER_INTERFACES,
+
+ /// This will parse the input as Subnet4 content.
+ PARSER_SUBNET4,
+
+ /// This will parse the input as pool4 content.
+ PARSER_POOL4,
+
+ /// This will parse the input as host-reservation.
+ PARSER_HOST_RESERVATION,
+
+ /// This will parse the input option definitions (for tests).
+ PARSER_OPTION_DEFS,
+
+ /// This will parse the input as option definition.
+ PARSER_OPTION_DEF,
+
+ /// This will parse the input as option data.
+ PARSER_OPTION_DATA,
+
+ /// This will parse the input as hooks-library.
+ PARSER_HOOKS_LIBRARY,
+
+ /// This will parse the input as dhcp-ddns.
+ PARSER_DHCP_DDNS,
+
+ /// This will parse the input as config-control.
+ PARSER_CONFIG_CONTROL,
+ } ParserType;
+
+ /// @brief Default constructor.
+ Parser4Context();
+
+ /// @brief destructor
+ virtual ~Parser4Context();
+
+ /// @brief JSON elements being parsed.
+ std::vector<isc::data::ElementPtr> stack_;
+
+ /// @brief Method called before scanning starts on a string.
+ ///
+ /// @param str string to be parsed
+ /// @param type specifies expected content
+ void scanStringBegin(const std::string& str, ParserType type);
+
+ /// @brief Method called before scanning starts on a file.
+ ///
+ /// @param f stdio FILE pointer
+ /// @param filename file to be parsed
+ /// @param type specifies expected content
+ void scanFileBegin(FILE* f, const std::string& filename, ParserType type);
+
+ /// @brief Method called after the last tokens are scanned.
+ void scanEnd();
+
+ /// @brief Divert input to an include file.
+ ///
+ /// @param filename file to be included
+ void includeFile(const std::string& filename);
+
+ /// @brief Run the parser on the string specified.
+ ///
+ /// This method parses specified string. Depending on the value of
+ /// parser_type, parser may either check only that the input is valid
+ /// JSON, or may do more specific syntax checking. See @ref ParserType
+ /// for supported syntax checkers.
+ ///
+ /// @param str string to be parsed
+ /// @param parser_type specifies expected content (usually DHCP4 or generic JSON)
+ /// @return Element structure representing parsed text.
+ isc::data::ElementPtr parseString(const std::string& str,
+ ParserType parser_type);
+
+ /// @brief Run the parser on the file specified.
+ ///
+ /// This method parses specified file. Depending on the value of
+ /// parser_type, parser may either check only that the input is valid
+ /// JSON, or may do more specific syntax checking. See @ref ParserType
+ /// for supported syntax checkers.
+ ///
+ /// @param filename file to be parsed
+ /// @param parser_type specifies expected content (usually DHCP4 or generic JSON)
+ /// @return Element structure representing parsed text.
+ isc::data::ElementPtr parseFile(const std::string& filename,
+ ParserType parser_type);
+
+ /// @brief Error handler
+ ///
+ /// @note The optional position for an error in a string begins by 1
+ /// so the caller should add 1 to the position of the C++ string.
+ ///
+ /// @param loc location within the parsed file where the problem was experienced.
+ /// @param what string explaining the nature of the error.
+ /// @param pos optional position for in string errors.
+ /// @throw Dhcp4ParseError
+ void error(const isc::dhcp::location& loc,
+ const std::string& what,
+ size_t pos = 0);
+
+ /// @brief Error handler
+ ///
+ /// This is a simplified error reporting tool for possible future
+ /// cases when the Dhcp4Parser is not able to handle the packet.
+ ///
+ /// @param what string explaining the nature of the error.
+ /// @throw Dhcp4ParseError
+ void error(const std::string& what);
+
+ /// @brief Fatal error handler
+ ///
+ /// This is for should not happen but fatal errors.
+ /// Used by YY_FATAL_ERROR macro so required to be static.
+ ///
+ /// @param what string explaining the nature of the error.
+ /// @throw Dhcp4ParseError
+ static void fatal(const std::string& what);
+
+ /// @brief Converts bison's position to one understandable by isc::data::Element
+ ///
+ /// Convert a bison location into an element position
+ /// (take the begin, the end is lost)
+ ///
+ /// @param loc location in bison format
+ /// @return Position in format accepted by Element
+ isc::data::Element::Position loc2pos(isc::dhcp::location& loc);
+
+ /// @brief Check if a required parameter is present
+ ///
+ /// Check if a required parameter is present in the map at the top
+ /// of the stack and raise an error when it is not.
+ ///
+ /// @param name name of the parameter to check
+ /// @param open_loc location of the opening curly bracket
+ /// @param close_loc location of the closing curly bracket
+ /// @throw Dhcp4ParseError
+ void require(const std::string& name,
+ isc::data::Element::Position open_loc,
+ isc::data::Element::Position close_loc);
+
+ /// @brief Check if a parameter is already present
+ ///
+ /// Check if a parameter is already present in the map at the top
+ /// of the stack and raise an error when it is.
+ ///
+ /// @param name name of the parameter to check
+ /// @param loc location of the current parameter
+ /// @throw Dhcp4ParseError
+ void unique(const std::string& name,
+ isc::data::Element::Position loc);
+
+ /// @brief Warning handler
+ ///
+ /// @param loc location within the parsed file where the problem was experienced
+ /// @param what string explaining the nature of the error
+ ///
+ /// @throw ParseError
+ void warning(const isc::dhcp::location& loc, const std::string& what);
+
+ /// @brief Warning for extra commas
+ ///
+ /// @param loc location within the parsed file of the extra comma
+ ///
+ /// @throw ParseError
+ void warnAboutExtraCommas(const isc::dhcp::location& loc);
+
+ /// @brief Defines syntactic contexts for lexical tie-ins
+ typedef enum {
+ ///< This one is used in pure JSON mode.
+ NO_KEYWORD,
+
+ ///< Used while parsing top level (that contains Dhcp4)
+ CONFIG,
+
+ ///< Used while parsing content of Dhcp4.
+ DHCP4,
+
+ /// Used while parsing Dhcp4/interfaces structures.
+ INTERFACES_CONFIG,
+
+ /// Sanity checks.
+ SANITY_CHECKS,
+
+ /// Used while parsing Dhcp4/interfaces/dhcp-socket-type structures.
+ DHCP_SOCKET_TYPE,
+
+ /// Used while parsing Dhcp4/interfaces/outbound-interface structures.
+ OUTBOUND_INTERFACE,
+
+ /// Used while parsing Dhcp4/lease-database structures.
+ LEASE_DATABASE,
+
+ /// Used while parsing Dhcp4/hosts-database[s] structures.
+ HOSTS_DATABASE,
+
+ /// Used while parsing Dhcp4/*-database/type.
+ DATABASE_TYPE,
+
+ /// Used while parsing Dhcp4/*-database/on-fail.
+ DATABASE_ON_FAIL,
+
+ /// Used while parsing Dhcp4/host-reservation-identifiers.
+ HOST_RESERVATION_IDENTIFIERS,
+
+ /// Used while parsing Dhcp4/hooks-libraries.
+ HOOKS_LIBRARIES,
+
+ /// Used while parsing Dhcp4/Subnet4 structures.
+ SUBNET4,
+
+ /// Used while parsing shared-networks structures.
+ SHARED_NETWORK,
+
+ /// Used while parsing Dhcp4/reservation-mode.
+ RESERVATION_MODE,
+
+ /// Used while parsing Dhcp4/option-def structures.
+ OPTION_DEF,
+
+ /// Used while parsing Dhcp4/option-data, Dhcp4/subnet4/option-data
+ /// or anywhere option-data is present (client classes, host
+ /// reservations and possibly others).
+ OPTION_DATA,
+
+ /// Used while parsing Dhcp4/client-classes structures.
+ CLIENT_CLASSES,
+
+ /// Used while parsing Dhcp4/expired-leases-processing.
+ EXPIRED_LEASES_PROCESSING,
+
+ /// Used while parsing Dhcp4/server-id structures.
+ SERVER_ID,
+
+ /// Used while parsing Dhcp4/control-socket structures.
+ CONTROL_SOCKET,
+
+ /// Used while parsing Dhcp4/dhcp-queue-control structures.
+ DHCP_QUEUE_CONTROL,
+
+ /// Used while parsing Dhcp4/multi-threading structures.
+ DHCP_MULTI_THREADING,
+
+ /// Used while parsing Dhcp4/subnet4/pools structures.
+ POOLS,
+
+ /// Used while parsing Dhcp4/reservations structures.
+ RESERVATIONS,
+
+ /// Used while parsing Dhcp4/subnet4relay structures.
+ RELAY,
+
+ /// Used while parsing Dhcp4/loggers structures.
+ LOGGERS,
+
+ /// Used while parsing Dhcp4/loggers/output_options structures.
+ OUTPUT_OPTIONS,
+
+ /// Used while parsing Dhcp4/dhcp-ddns.
+ DHCP_DDNS,
+
+ /// Used while parsing Dhcp4/dhcp-ddns/ncr-protocol
+ NCR_PROTOCOL,
+
+ /// Used while parsing Dhcp4/dhcp-ddns/ncr-format
+ NCR_FORMAT,
+
+ /// Used while parsing Dhcp4/dhcp-ddns/replace-client-name.
+ REPLACE_CLIENT_NAME,
+
+ /// Used while parsing Dhcp4/config-control
+ CONFIG_CONTROL,
+
+ /// Used while parsing config-control/config-databases
+ CONFIG_DATABASE,
+
+ /// Used while parsing compatibility parameters
+ COMPATIBILITY,
+
+ } ParserContext;
+
+ /// @brief File name
+ std::string file_;
+
+ /// @brief File name stack
+ std::vector<std::string> files_;
+
+ /// @brief Location of the current token
+ ///
+ /// The lexer will keep updating it. This variable will be useful
+ /// for logging errors.
+ isc::dhcp::location loc_;
+
+ /// @brief Location stack
+ std::vector<isc::dhcp::location> locs_;
+
+ /// @brief Lexer state stack
+ std::vector<struct yy_buffer_state*> states_;
+
+ /// @brief sFile (aka FILE)
+ FILE* sfile_;
+
+ /// @brief sFile (aka FILE) stack
+ ///
+ /// This is a stack of files. Typically there's only one file (the
+ /// one being currently parsed), but there may be more if one
+ /// file includes another.
+ std::vector<FILE*> sfiles_;
+
+ /// @brief Current syntactic context
+ ParserContext ctx_;
+
+ /// @brief Enter a new syntactic context
+ ///
+ /// Entering a new syntactic context is useful in several ways.
+ /// First, it allows the parser to avoid conflicts. Second, it
+ /// allows the lexer to return different tokens depending on
+ /// context (e.g. if "renew-timer" string is detected, the lexer
+ /// will return STRING token if in JSON mode or RENEW_TIMER if
+ /// in DHCP4 mode. Finally, the syntactic context allows the
+ /// error message to be more descriptive if the input string
+ /// does not parse properly.
+ ///
+ /// @param ctx the syntactic context to enter into
+ void enter(const ParserContext& ctx);
+
+ /// @brief Leave a syntactic context
+ ///
+ /// @throw isc::Unexpected if unbalanced
+ void leave();
+
+ /// @brief Get the syntactic context name
+ ///
+ /// @return printable name of the context.
+ const std::string contextName();
+
+ private:
+ /// @brief Flag determining scanner debugging.
+ bool trace_scanning_;
+
+ /// @brief Flag determining parser debugging.
+ bool trace_parsing_;
+
+ /// @brief Syntactic context stack
+ std::vector<ParserContext> cstack_;
+
+ /// @brief Common part of parseXXX
+ ///
+ /// @return Element structure representing parsed text.
+ isc::data::ElementPtr parseCommon();
+};
+
+}; // end of isc::eval namespace
+}; // end of isc namespace
+
+#endif
diff --git a/src/bin/dhcp4/parser_context_decl.h b/src/bin/dhcp4/parser_context_decl.h
new file mode 100644
index 0000000..039a790
--- /dev/null
+++ b/src/bin/dhcp4/parser_context_decl.h
@@ -0,0 +1,20 @@
+// Copyright (C) 2016 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 PARSER4_CONTEXT_DECL_H
+#define PARSER4_CONTEXT_DECL_H
+
+/// @file dhcp4/parser_context_decl.h Forward declaration of the ParserContext class
+
+namespace isc {
+namespace dhcp {
+
+class Parser4Context;
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif
diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am
new file mode 100644
index 0000000..8e5808b
--- /dev/null
+++ b/src/bin/dhcp4/tests/Makefile.am
@@ -0,0 +1,186 @@
+SUBDIRS = .
+
+# Add to the tarball:
+EXTRA_DIST = get_config_unittest.cc.skel
+
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+# Shell tests
+SHTESTS = dhcp4_process_tests.sh
+
+# Run shell tests on "make check".
+check_SCRIPTS = $(SHTESTS)
+TESTS = $(SHTESTS)
+
+# 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
+
+# Don't install tests.
+noinst_SCRIPTS = $(SHTESTS)
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_srcdir)/src -I$(top_builddir)/src
+AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp4/tests\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+AM_CPPFLAGS += -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples/kea4\"
+AM_CPPFLAGS += -DSYNTAX_FILE=\"$(abs_srcdir)/../dhcp4_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 = dhcp4_unittests
+
+dhcp4_unittests_SOURCES = d2_unittest.h d2_unittest.cc
+dhcp4_unittests_SOURCES += dhcp4_unittests.cc
+dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc
+dhcp4_unittests_SOURCES += dhcp4_test_utils.cc dhcp4_test_utils.h
+dhcp4_unittests_SOURCES += direct_client_unittest.cc
+dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
+dhcp4_unittests_SOURCES += classify_unittest.cc
+dhcp4_unittests_SOURCES += config_backend_unittest.cc
+dhcp4_unittests_SOURCES += config_parser_unittest.cc
+dhcp4_unittests_SOURCES += fqdn_unittest.cc
+dhcp4_unittests_SOURCES += marker_file.cc
+dhcp4_unittests_SOURCES += dhcp4_client.cc dhcp4_client.h
+dhcp4_unittests_SOURCES += hooks_unittest.cc
+dhcp4_unittests_SOURCES += inform_unittest.cc
+dhcp4_unittests_SOURCES += dora_unittest.cc
+dhcp4_unittests_SOURCES += host_options_unittest.cc
+dhcp4_unittests_SOURCES += release_unittest.cc
+dhcp4_unittests_SOURCES += parser_unittest.cc
+dhcp4_unittests_SOURCES += out_of_range_unittest.cc
+dhcp4_unittests_SOURCES += decline_unittest.cc
+dhcp4_unittests_SOURCES += kea_controller_unittest.cc
+dhcp4_unittests_SOURCES += dhcp4to6_ipc_unittest.cc
+dhcp4_unittests_SOURCES += simple_parser4_unittest.cc
+dhcp4_unittests_SOURCES += get_config_unittest.cc get_config_unittest.h
+dhcp4_unittests_SOURCES += shared_network_unittest.cc
+dhcp4_unittests_SOURCES += host_unittest.cc
+dhcp4_unittests_SOURCES += vendor_opts_unittest.cc
+dhcp4_unittests_SOURCES += client_handler_unittest.cc
+
+nodist_dhcp4_unittests_SOURCES = marker_file.h test_libraries.h
+
+dhcp4_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+dhcp4_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+if HAVE_MYSQL
+dhcp4_unittests_LDFLAGS += $(MYSQL_LIBS)
+endif
+if HAVE_PGSQL
+dhcp4_unittests_LDFLAGS += $(PGSQL_LIBS)
+endif
+dhcp4_unittests_LDFLAGS += $(GTEST_LDFLAGS)
+
+dhcp4_unittests_LDADD = $(top_builddir)/src/bin/dhcp4/libdhcp4.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/process/libkea-process.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/http/libkea-http.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+
+if HAVE_MYSQL
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/mysql/testutils/libmysqltest.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/mysql/libkea-mysql.la
+endif
+if HAVE_PGSQL
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/pgsql/testutils/libpgsqltest.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/pgsql/libkea-pgsql.la
+endif
+
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/database/testutils/libdatabasetest.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/database/libkea-database.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
+
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+dhcp4_unittests_LDADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS)
+dhcp4_unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD)
+
+# Run C++ tests on "make check".
+TESTS += $(PROGRAM_TESTS)
+
+# 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
+ ./dhcp4_unittests --gtest_filter="Dhcp4Parser*" > /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
+ ./dhcp4_unittests --gtest_filter="Dhcp4GetConfig*" > /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
diff --git a/src/bin/dhcp4/tests/Makefile.in b/src/bin/dhcp4/tests/Makefile.in
new file mode 100644
index 0000000..3593c01
--- /dev/null
+++ b/src/bin/dhcp4/tests/Makefile.in
@@ -0,0 +1,1742 @@
+# 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@
+TESTS = $(SHTESTS) $(am__EXEEXT_2)
+@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_MYSQL_TRUE@am__append_3 = $(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@@HAVE_PGSQL_TRUE@am__append_4 = $(top_builddir)/src/lib/pgsql/testutils/libpgsqltest.la \
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@ $(top_builddir)/src/lib/pgsql/libkea-pgsql.la
+
+# Run C++ tests on "make check".
+@HAVE_GTEST_TRUE@am__append_5 = $(PROGRAM_TESTS)
+@HAVE_GTEST_TRUE@noinst_PROGRAMS = $(am__EXEEXT_1)
+subdir = src/bin/dhcp4/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_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_sysrepo.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 = dhcp4_process_tests.sh marker_file.h \
+ test_data_files_config.h test_libraries.h
+CONFIG_CLEAN_VPATH_FILES =
+@HAVE_GTEST_TRUE@am__EXEEXT_1 = dhcp4_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__dhcp4_unittests_SOURCES_DIST = d2_unittest.h d2_unittest.cc \
+ dhcp4_unittests.cc dhcp4_srv_unittest.cc dhcp4_test_utils.cc \
+ dhcp4_test_utils.h direct_client_unittest.cc \
+ ctrl_dhcp4_srv_unittest.cc classify_unittest.cc \
+ config_backend_unittest.cc config_parser_unittest.cc \
+ fqdn_unittest.cc marker_file.cc dhcp4_client.cc dhcp4_client.h \
+ hooks_unittest.cc inform_unittest.cc dora_unittest.cc \
+ host_options_unittest.cc release_unittest.cc \
+ parser_unittest.cc out_of_range_unittest.cc \
+ decline_unittest.cc kea_controller_unittest.cc \
+ dhcp4to6_ipc_unittest.cc simple_parser4_unittest.cc \
+ get_config_unittest.cc get_config_unittest.h \
+ shared_network_unittest.cc host_unittest.cc \
+ vendor_opts_unittest.cc client_handler_unittest.cc
+@HAVE_GTEST_TRUE@am_dhcp4_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-d2_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-dhcp4_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-dhcp4_srv_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-dhcp4_test_utils.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-direct_client_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-ctrl_dhcp4_srv_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-classify_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-config_backend_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-config_parser_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-fqdn_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-marker_file.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-dhcp4_client.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-hooks_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-inform_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-dora_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-host_options_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-release_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-parser_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-out_of_range_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-decline_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-kea_controller_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-dhcp4to6_ipc_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-simple_parser4_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-get_config_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-shared_network_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-host_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-vendor_opts_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp4_unittests-client_handler_unittest.$(OBJEXT)
+nodist_dhcp4_unittests_OBJECTS =
+dhcp4_unittests_OBJECTS = $(am_dhcp4_unittests_OBJECTS) \
+ $(nodist_dhcp4_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@dhcp4_unittests_DEPENDENCIES = \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/bin/dhcp4/libdhcp4.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/libkea-process.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cfgrpt/libcfgrpt.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)
+dhcp4_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(dhcp4_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)/dhcp4_unittests-classify_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-client_handler_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-config_backend_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-config_parser_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-ctrl_dhcp4_srv_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-d2_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-decline_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-dhcp4_client.Po \
+ ./$(DEPDIR)/dhcp4_unittests-dhcp4_srv_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-dhcp4_test_utils.Po \
+ ./$(DEPDIR)/dhcp4_unittests-dhcp4_unittests.Po \
+ ./$(DEPDIR)/dhcp4_unittests-dhcp4to6_ipc_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-direct_client_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-dora_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-fqdn_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-get_config_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-hooks_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-host_options_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-host_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-inform_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-kea_controller_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-marker_file.Po \
+ ./$(DEPDIR)/dhcp4_unittests-out_of_range_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-parser_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-release_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-shared_network_unittest.Po \
+ ./$(DEPDIR)/dhcp4_unittests-simple_parser4_unittest.Po \
+ ./$(DEPDIR)/dhcp4_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) $(dhcp4_unittests_SOURCES) \
+ $(nodist_dhcp4_unittests_SOURCES)
+DIST_SOURCES = $(libco1_la_SOURCES) $(libco2_la_SOURCES) \
+ $(libco3_la_SOURCES) $(am__dhcp4_unittests_SOURCES_DIST)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+@HAVE_GTEST_TRUE@am__EXEEXT_2 = $(am__EXEEXT_1)
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(srcdir)/dhcp4_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_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_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_SYSREPO = @HAVE_SYSREPO@
+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@
+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_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+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 = dhcp4_process_tests.sh
+
+# Run shell tests on "make check".
+check_SCRIPTS = $(SHTESTS)
+
+# 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
+
+# Don't install tests.
+noinst_SCRIPTS = $(SHTESTS)
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ -I$(top_srcdir)/src -I$(top_builddir)/src \
+ -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin \
+ $(BOOST_INCLUDES) \
+ -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp4/tests\" \
+ -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\" \
+ -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples/kea4\" \
+ -DSYNTAX_FILE=\"$(abs_srcdir)/../dhcp4_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 = dhcp4_unittests
+@HAVE_GTEST_TRUE@dhcp4_unittests_SOURCES = d2_unittest.h \
+@HAVE_GTEST_TRUE@ d2_unittest.cc dhcp4_unittests.cc \
+@HAVE_GTEST_TRUE@ dhcp4_srv_unittest.cc dhcp4_test_utils.cc \
+@HAVE_GTEST_TRUE@ dhcp4_test_utils.h direct_client_unittest.cc \
+@HAVE_GTEST_TRUE@ ctrl_dhcp4_srv_unittest.cc \
+@HAVE_GTEST_TRUE@ classify_unittest.cc \
+@HAVE_GTEST_TRUE@ config_backend_unittest.cc \
+@HAVE_GTEST_TRUE@ config_parser_unittest.cc fqdn_unittest.cc \
+@HAVE_GTEST_TRUE@ marker_file.cc dhcp4_client.cc dhcp4_client.h \
+@HAVE_GTEST_TRUE@ hooks_unittest.cc inform_unittest.cc \
+@HAVE_GTEST_TRUE@ dora_unittest.cc host_options_unittest.cc \
+@HAVE_GTEST_TRUE@ release_unittest.cc parser_unittest.cc \
+@HAVE_GTEST_TRUE@ out_of_range_unittest.cc decline_unittest.cc \
+@HAVE_GTEST_TRUE@ kea_controller_unittest.cc \
+@HAVE_GTEST_TRUE@ dhcp4to6_ipc_unittest.cc \
+@HAVE_GTEST_TRUE@ simple_parser4_unittest.cc \
+@HAVE_GTEST_TRUE@ get_config_unittest.cc get_config_unittest.h \
+@HAVE_GTEST_TRUE@ shared_network_unittest.cc host_unittest.cc \
+@HAVE_GTEST_TRUE@ vendor_opts_unittest.cc \
+@HAVE_GTEST_TRUE@ client_handler_unittest.cc
+@HAVE_GTEST_TRUE@nodist_dhcp4_unittests_SOURCES = marker_file.h test_libraries.h
+@HAVE_GTEST_TRUE@dhcp4_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@dhcp4_unittests_LDFLAGS = $(AM_LDFLAGS) \
+@HAVE_GTEST_TRUE@ $(CRYPTO_LDFLAGS) $(am__append_1) \
+@HAVE_GTEST_TRUE@ $(am__append_2) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@dhcp4_unittests_LDADD = \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/bin/dhcp4/libdhcp4.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/libkea-process.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cfgrpt/libcfgrpt.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)
+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/dhcp4/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/bin/dhcp4/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):
+dhcp4_process_tests.sh: $(top_builddir)/config.status $(srcdir)/dhcp4_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)
+
+dhcp4_unittests$(EXEEXT): $(dhcp4_unittests_OBJECTS) $(dhcp4_unittests_DEPENDENCIES) $(EXTRA_dhcp4_unittests_DEPENDENCIES)
+ @rm -f dhcp4_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(dhcp4_unittests_LINK) $(dhcp4_unittests_OBJECTS) $(dhcp4_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-classify_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-client_handler_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-config_backend_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-config_parser_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-ctrl_dhcp4_srv_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-d2_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-decline_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-dhcp4_client.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-dhcp4_srv_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-dhcp4_test_utils.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-dhcp4_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-dhcp4to6_ipc_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-direct_client_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-dora_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-fqdn_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-get_config_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-hooks_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-host_options_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-host_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-inform_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-kea_controller_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-marker_file.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-out_of_range_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-parser_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-release_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-shared_network_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_unittests-simple_parser4_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4_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
+
+dhcp4_unittests-d2_unittest.o: d2_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-d2_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-d2_unittest.Tpo -c -o dhcp4_unittests-d2_unittest.o `test -f 'd2_unittest.cc' || echo '$(srcdir)/'`d2_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-d2_unittest.Tpo $(DEPDIR)/dhcp4_unittests-d2_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-d2_unittest.o `test -f 'd2_unittest.cc' || echo '$(srcdir)/'`d2_unittest.cc
+
+dhcp4_unittests-d2_unittest.obj: d2_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-d2_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-d2_unittest.Tpo -c -o dhcp4_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)/dhcp4_unittests-d2_unittest.Tpo $(DEPDIR)/dhcp4_unittests-d2_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-d2_unittest.obj `if test -f 'd2_unittest.cc'; then $(CYGPATH_W) 'd2_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/d2_unittest.cc'; fi`
+
+dhcp4_unittests-dhcp4_unittests.o: dhcp4_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-dhcp4_unittests.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-dhcp4_unittests.Tpo -c -o dhcp4_unittests-dhcp4_unittests.o `test -f 'dhcp4_unittests.cc' || echo '$(srcdir)/'`dhcp4_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-dhcp4_unittests.Tpo $(DEPDIR)/dhcp4_unittests-dhcp4_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp4_unittests.cc' object='dhcp4_unittests-dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-dhcp4_unittests.o `test -f 'dhcp4_unittests.cc' || echo '$(srcdir)/'`dhcp4_unittests.cc
+
+dhcp4_unittests-dhcp4_unittests.obj: dhcp4_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-dhcp4_unittests.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-dhcp4_unittests.Tpo -c -o dhcp4_unittests-dhcp4_unittests.obj `if test -f 'dhcp4_unittests.cc'; then $(CYGPATH_W) 'dhcp4_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp4_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-dhcp4_unittests.Tpo $(DEPDIR)/dhcp4_unittests-dhcp4_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp4_unittests.cc' object='dhcp4_unittests-dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-dhcp4_unittests.obj `if test -f 'dhcp4_unittests.cc'; then $(CYGPATH_W) 'dhcp4_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp4_unittests.cc'; fi`
+
+dhcp4_unittests-dhcp4_srv_unittest.o: dhcp4_srv_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-dhcp4_srv_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-dhcp4_srv_unittest.Tpo -c -o dhcp4_unittests-dhcp4_srv_unittest.o `test -f 'dhcp4_srv_unittest.cc' || echo '$(srcdir)/'`dhcp4_srv_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-dhcp4_srv_unittest.Tpo $(DEPDIR)/dhcp4_unittests-dhcp4_srv_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp4_srv_unittest.cc' object='dhcp4_unittests-dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-dhcp4_srv_unittest.o `test -f 'dhcp4_srv_unittest.cc' || echo '$(srcdir)/'`dhcp4_srv_unittest.cc
+
+dhcp4_unittests-dhcp4_srv_unittest.obj: dhcp4_srv_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-dhcp4_srv_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-dhcp4_srv_unittest.Tpo -c -o dhcp4_unittests-dhcp4_srv_unittest.obj `if test -f 'dhcp4_srv_unittest.cc'; then $(CYGPATH_W) 'dhcp4_srv_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp4_srv_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-dhcp4_srv_unittest.Tpo $(DEPDIR)/dhcp4_unittests-dhcp4_srv_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp4_srv_unittest.cc' object='dhcp4_unittests-dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-dhcp4_srv_unittest.obj `if test -f 'dhcp4_srv_unittest.cc'; then $(CYGPATH_W) 'dhcp4_srv_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp4_srv_unittest.cc'; fi`
+
+dhcp4_unittests-dhcp4_test_utils.o: dhcp4_test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-dhcp4_test_utils.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-dhcp4_test_utils.Tpo -c -o dhcp4_unittests-dhcp4_test_utils.o `test -f 'dhcp4_test_utils.cc' || echo '$(srcdir)/'`dhcp4_test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-dhcp4_test_utils.Tpo $(DEPDIR)/dhcp4_unittests-dhcp4_test_utils.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp4_test_utils.cc' object='dhcp4_unittests-dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-dhcp4_test_utils.o `test -f 'dhcp4_test_utils.cc' || echo '$(srcdir)/'`dhcp4_test_utils.cc
+
+dhcp4_unittests-dhcp4_test_utils.obj: dhcp4_test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-dhcp4_test_utils.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-dhcp4_test_utils.Tpo -c -o dhcp4_unittests-dhcp4_test_utils.obj `if test -f 'dhcp4_test_utils.cc'; then $(CYGPATH_W) 'dhcp4_test_utils.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp4_test_utils.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-dhcp4_test_utils.Tpo $(DEPDIR)/dhcp4_unittests-dhcp4_test_utils.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp4_test_utils.cc' object='dhcp4_unittests-dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-dhcp4_test_utils.obj `if test -f 'dhcp4_test_utils.cc'; then $(CYGPATH_W) 'dhcp4_test_utils.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp4_test_utils.cc'; fi`
+
+dhcp4_unittests-direct_client_unittest.o: direct_client_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-direct_client_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-direct_client_unittest.Tpo -c -o dhcp4_unittests-direct_client_unittest.o `test -f 'direct_client_unittest.cc' || echo '$(srcdir)/'`direct_client_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-direct_client_unittest.Tpo $(DEPDIR)/dhcp4_unittests-direct_client_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='direct_client_unittest.cc' object='dhcp4_unittests-direct_client_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-direct_client_unittest.o `test -f 'direct_client_unittest.cc' || echo '$(srcdir)/'`direct_client_unittest.cc
+
+dhcp4_unittests-direct_client_unittest.obj: direct_client_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-direct_client_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-direct_client_unittest.Tpo -c -o dhcp4_unittests-direct_client_unittest.obj `if test -f 'direct_client_unittest.cc'; then $(CYGPATH_W) 'direct_client_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/direct_client_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-direct_client_unittest.Tpo $(DEPDIR)/dhcp4_unittests-direct_client_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='direct_client_unittest.cc' object='dhcp4_unittests-direct_client_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-direct_client_unittest.obj `if test -f 'direct_client_unittest.cc'; then $(CYGPATH_W) 'direct_client_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/direct_client_unittest.cc'; fi`
+
+dhcp4_unittests-ctrl_dhcp4_srv_unittest.o: ctrl_dhcp4_srv_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-ctrl_dhcp4_srv_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-ctrl_dhcp4_srv_unittest.Tpo -c -o dhcp4_unittests-ctrl_dhcp4_srv_unittest.o `test -f 'ctrl_dhcp4_srv_unittest.cc' || echo '$(srcdir)/'`ctrl_dhcp4_srv_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-ctrl_dhcp4_srv_unittest.Tpo $(DEPDIR)/dhcp4_unittests-ctrl_dhcp4_srv_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ctrl_dhcp4_srv_unittest.cc' object='dhcp4_unittests-ctrl_dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-ctrl_dhcp4_srv_unittest.o `test -f 'ctrl_dhcp4_srv_unittest.cc' || echo '$(srcdir)/'`ctrl_dhcp4_srv_unittest.cc
+
+dhcp4_unittests-ctrl_dhcp4_srv_unittest.obj: ctrl_dhcp4_srv_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-ctrl_dhcp4_srv_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-ctrl_dhcp4_srv_unittest.Tpo -c -o dhcp4_unittests-ctrl_dhcp4_srv_unittest.obj `if test -f 'ctrl_dhcp4_srv_unittest.cc'; then $(CYGPATH_W) 'ctrl_dhcp4_srv_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/ctrl_dhcp4_srv_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-ctrl_dhcp4_srv_unittest.Tpo $(DEPDIR)/dhcp4_unittests-ctrl_dhcp4_srv_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ctrl_dhcp4_srv_unittest.cc' object='dhcp4_unittests-ctrl_dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-ctrl_dhcp4_srv_unittest.obj `if test -f 'ctrl_dhcp4_srv_unittest.cc'; then $(CYGPATH_W) 'ctrl_dhcp4_srv_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/ctrl_dhcp4_srv_unittest.cc'; fi`
+
+dhcp4_unittests-classify_unittest.o: classify_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-classify_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-classify_unittest.Tpo -c -o dhcp4_unittests-classify_unittest.o `test -f 'classify_unittest.cc' || echo '$(srcdir)/'`classify_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-classify_unittest.Tpo $(DEPDIR)/dhcp4_unittests-classify_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='classify_unittest.cc' object='dhcp4_unittests-classify_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-classify_unittest.o `test -f 'classify_unittest.cc' || echo '$(srcdir)/'`classify_unittest.cc
+
+dhcp4_unittests-classify_unittest.obj: classify_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-classify_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-classify_unittest.Tpo -c -o dhcp4_unittests-classify_unittest.obj `if test -f 'classify_unittest.cc'; then $(CYGPATH_W) 'classify_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/classify_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-classify_unittest.Tpo $(DEPDIR)/dhcp4_unittests-classify_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='classify_unittest.cc' object='dhcp4_unittests-classify_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-classify_unittest.obj `if test -f 'classify_unittest.cc'; then $(CYGPATH_W) 'classify_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/classify_unittest.cc'; fi`
+
+dhcp4_unittests-config_backend_unittest.o: config_backend_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-config_backend_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-config_backend_unittest.Tpo -c -o dhcp4_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)/dhcp4_unittests-config_backend_unittest.Tpo $(DEPDIR)/dhcp4_unittests-config_backend_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_backend_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-config_backend_unittest.o `test -f 'config_backend_unittest.cc' || echo '$(srcdir)/'`config_backend_unittest.cc
+
+dhcp4_unittests-config_backend_unittest.obj: config_backend_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-config_backend_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-config_backend_unittest.Tpo -c -o dhcp4_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)/dhcp4_unittests-config_backend_unittest.Tpo $(DEPDIR)/dhcp4_unittests-config_backend_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_backend_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_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`
+
+dhcp4_unittests-config_parser_unittest.o: config_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-config_parser_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-config_parser_unittest.Tpo -c -o dhcp4_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)/dhcp4_unittests-config_parser_unittest.Tpo $(DEPDIR)/dhcp4_unittests-config_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_parser_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-config_parser_unittest.o `test -f 'config_parser_unittest.cc' || echo '$(srcdir)/'`config_parser_unittest.cc
+
+dhcp4_unittests-config_parser_unittest.obj: config_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-config_parser_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-config_parser_unittest.Tpo -c -o dhcp4_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)/dhcp4_unittests-config_parser_unittest.Tpo $(DEPDIR)/dhcp4_unittests-config_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_parser_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_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`
+
+dhcp4_unittests-fqdn_unittest.o: fqdn_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-fqdn_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-fqdn_unittest.Tpo -c -o dhcp4_unittests-fqdn_unittest.o `test -f 'fqdn_unittest.cc' || echo '$(srcdir)/'`fqdn_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-fqdn_unittest.Tpo $(DEPDIR)/dhcp4_unittests-fqdn_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='fqdn_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-fqdn_unittest.o `test -f 'fqdn_unittest.cc' || echo '$(srcdir)/'`fqdn_unittest.cc
+
+dhcp4_unittests-fqdn_unittest.obj: fqdn_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-fqdn_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-fqdn_unittest.Tpo -c -o dhcp4_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)/dhcp4_unittests-fqdn_unittest.Tpo $(DEPDIR)/dhcp4_unittests-fqdn_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='fqdn_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-fqdn_unittest.obj `if test -f 'fqdn_unittest.cc'; then $(CYGPATH_W) 'fqdn_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/fqdn_unittest.cc'; fi`
+
+dhcp4_unittests-marker_file.o: marker_file.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-marker_file.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-marker_file.Tpo -c -o dhcp4_unittests-marker_file.o `test -f 'marker_file.cc' || echo '$(srcdir)/'`marker_file.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-marker_file.Tpo $(DEPDIR)/dhcp4_unittests-marker_file.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='marker_file.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-marker_file.o `test -f 'marker_file.cc' || echo '$(srcdir)/'`marker_file.cc
+
+dhcp4_unittests-marker_file.obj: marker_file.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-marker_file.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-marker_file.Tpo -c -o dhcp4_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)/dhcp4_unittests-marker_file.Tpo $(DEPDIR)/dhcp4_unittests-marker_file.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='marker_file.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-marker_file.obj `if test -f 'marker_file.cc'; then $(CYGPATH_W) 'marker_file.cc'; else $(CYGPATH_W) '$(srcdir)/marker_file.cc'; fi`
+
+dhcp4_unittests-dhcp4_client.o: dhcp4_client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-dhcp4_client.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-dhcp4_client.Tpo -c -o dhcp4_unittests-dhcp4_client.o `test -f 'dhcp4_client.cc' || echo '$(srcdir)/'`dhcp4_client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-dhcp4_client.Tpo $(DEPDIR)/dhcp4_unittests-dhcp4_client.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp4_client.cc' object='dhcp4_unittests-dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-dhcp4_client.o `test -f 'dhcp4_client.cc' || echo '$(srcdir)/'`dhcp4_client.cc
+
+dhcp4_unittests-dhcp4_client.obj: dhcp4_client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-dhcp4_client.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-dhcp4_client.Tpo -c -o dhcp4_unittests-dhcp4_client.obj `if test -f 'dhcp4_client.cc'; then $(CYGPATH_W) 'dhcp4_client.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp4_client.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-dhcp4_client.Tpo $(DEPDIR)/dhcp4_unittests-dhcp4_client.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp4_client.cc' object='dhcp4_unittests-dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-dhcp4_client.obj `if test -f 'dhcp4_client.cc'; then $(CYGPATH_W) 'dhcp4_client.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp4_client.cc'; fi`
+
+dhcp4_unittests-hooks_unittest.o: hooks_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-hooks_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-hooks_unittest.Tpo -c -o dhcp4_unittests-hooks_unittest.o `test -f 'hooks_unittest.cc' || echo '$(srcdir)/'`hooks_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-hooks_unittest.Tpo $(DEPDIR)/dhcp4_unittests-hooks_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hooks_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-hooks_unittest.o `test -f 'hooks_unittest.cc' || echo '$(srcdir)/'`hooks_unittest.cc
+
+dhcp4_unittests-hooks_unittest.obj: hooks_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-hooks_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-hooks_unittest.Tpo -c -o dhcp4_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)/dhcp4_unittests-hooks_unittest.Tpo $(DEPDIR)/dhcp4_unittests-hooks_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hooks_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-hooks_unittest.obj `if test -f 'hooks_unittest.cc'; then $(CYGPATH_W) 'hooks_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hooks_unittest.cc'; fi`
+
+dhcp4_unittests-inform_unittest.o: inform_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-inform_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-inform_unittest.Tpo -c -o dhcp4_unittests-inform_unittest.o `test -f 'inform_unittest.cc' || echo '$(srcdir)/'`inform_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-inform_unittest.Tpo $(DEPDIR)/dhcp4_unittests-inform_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='inform_unittest.cc' object='dhcp4_unittests-inform_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-inform_unittest.o `test -f 'inform_unittest.cc' || echo '$(srcdir)/'`inform_unittest.cc
+
+dhcp4_unittests-inform_unittest.obj: inform_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-inform_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-inform_unittest.Tpo -c -o dhcp4_unittests-inform_unittest.obj `if test -f 'inform_unittest.cc'; then $(CYGPATH_W) 'inform_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/inform_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-inform_unittest.Tpo $(DEPDIR)/dhcp4_unittests-inform_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='inform_unittest.cc' object='dhcp4_unittests-inform_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-inform_unittest.obj `if test -f 'inform_unittest.cc'; then $(CYGPATH_W) 'inform_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/inform_unittest.cc'; fi`
+
+dhcp4_unittests-dora_unittest.o: dora_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-dora_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-dora_unittest.Tpo -c -o dhcp4_unittests-dora_unittest.o `test -f 'dora_unittest.cc' || echo '$(srcdir)/'`dora_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-dora_unittest.Tpo $(DEPDIR)/dhcp4_unittests-dora_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dora_unittest.cc' object='dhcp4_unittests-dora_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-dora_unittest.o `test -f 'dora_unittest.cc' || echo '$(srcdir)/'`dora_unittest.cc
+
+dhcp4_unittests-dora_unittest.obj: dora_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-dora_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-dora_unittest.Tpo -c -o dhcp4_unittests-dora_unittest.obj `if test -f 'dora_unittest.cc'; then $(CYGPATH_W) 'dora_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dora_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-dora_unittest.Tpo $(DEPDIR)/dhcp4_unittests-dora_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dora_unittest.cc' object='dhcp4_unittests-dora_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-dora_unittest.obj `if test -f 'dora_unittest.cc'; then $(CYGPATH_W) 'dora_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dora_unittest.cc'; fi`
+
+dhcp4_unittests-host_options_unittest.o: host_options_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-host_options_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-host_options_unittest.Tpo -c -o dhcp4_unittests-host_options_unittest.o `test -f 'host_options_unittest.cc' || echo '$(srcdir)/'`host_options_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-host_options_unittest.Tpo $(DEPDIR)/dhcp4_unittests-host_options_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_options_unittest.cc' object='dhcp4_unittests-host_options_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-host_options_unittest.o `test -f 'host_options_unittest.cc' || echo '$(srcdir)/'`host_options_unittest.cc
+
+dhcp4_unittests-host_options_unittest.obj: host_options_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-host_options_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-host_options_unittest.Tpo -c -o dhcp4_unittests-host_options_unittest.obj `if test -f 'host_options_unittest.cc'; then $(CYGPATH_W) 'host_options_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/host_options_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-host_options_unittest.Tpo $(DEPDIR)/dhcp4_unittests-host_options_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_options_unittest.cc' object='dhcp4_unittests-host_options_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-host_options_unittest.obj `if test -f 'host_options_unittest.cc'; then $(CYGPATH_W) 'host_options_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/host_options_unittest.cc'; fi`
+
+dhcp4_unittests-release_unittest.o: release_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-release_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-release_unittest.Tpo -c -o dhcp4_unittests-release_unittest.o `test -f 'release_unittest.cc' || echo '$(srcdir)/'`release_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-release_unittest.Tpo $(DEPDIR)/dhcp4_unittests-release_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='release_unittest.cc' object='dhcp4_unittests-release_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-release_unittest.o `test -f 'release_unittest.cc' || echo '$(srcdir)/'`release_unittest.cc
+
+dhcp4_unittests-release_unittest.obj: release_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-release_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-release_unittest.Tpo -c -o dhcp4_unittests-release_unittest.obj `if test -f 'release_unittest.cc'; then $(CYGPATH_W) 'release_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/release_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-release_unittest.Tpo $(DEPDIR)/dhcp4_unittests-release_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='release_unittest.cc' object='dhcp4_unittests-release_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-release_unittest.obj `if test -f 'release_unittest.cc'; then $(CYGPATH_W) 'release_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/release_unittest.cc'; fi`
+
+dhcp4_unittests-parser_unittest.o: parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-parser_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-parser_unittest.Tpo -c -o dhcp4_unittests-parser_unittest.o `test -f 'parser_unittest.cc' || echo '$(srcdir)/'`parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-parser_unittest.Tpo $(DEPDIR)/dhcp4_unittests-parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parser_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-parser_unittest.o `test -f 'parser_unittest.cc' || echo '$(srcdir)/'`parser_unittest.cc
+
+dhcp4_unittests-parser_unittest.obj: parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-parser_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-parser_unittest.Tpo -c -o dhcp4_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)/dhcp4_unittests-parser_unittest.Tpo $(DEPDIR)/dhcp4_unittests-parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parser_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-parser_unittest.obj `if test -f 'parser_unittest.cc'; then $(CYGPATH_W) 'parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/parser_unittest.cc'; fi`
+
+dhcp4_unittests-out_of_range_unittest.o: out_of_range_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-out_of_range_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-out_of_range_unittest.Tpo -c -o dhcp4_unittests-out_of_range_unittest.o `test -f 'out_of_range_unittest.cc' || echo '$(srcdir)/'`out_of_range_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-out_of_range_unittest.Tpo $(DEPDIR)/dhcp4_unittests-out_of_range_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='out_of_range_unittest.cc' object='dhcp4_unittests-out_of_range_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-out_of_range_unittest.o `test -f 'out_of_range_unittest.cc' || echo '$(srcdir)/'`out_of_range_unittest.cc
+
+dhcp4_unittests-out_of_range_unittest.obj: out_of_range_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-out_of_range_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-out_of_range_unittest.Tpo -c -o dhcp4_unittests-out_of_range_unittest.obj `if test -f 'out_of_range_unittest.cc'; then $(CYGPATH_W) 'out_of_range_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/out_of_range_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-out_of_range_unittest.Tpo $(DEPDIR)/dhcp4_unittests-out_of_range_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='out_of_range_unittest.cc' object='dhcp4_unittests-out_of_range_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-out_of_range_unittest.obj `if test -f 'out_of_range_unittest.cc'; then $(CYGPATH_W) 'out_of_range_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/out_of_range_unittest.cc'; fi`
+
+dhcp4_unittests-decline_unittest.o: decline_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-decline_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-decline_unittest.Tpo -c -o dhcp4_unittests-decline_unittest.o `test -f 'decline_unittest.cc' || echo '$(srcdir)/'`decline_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-decline_unittest.Tpo $(DEPDIR)/dhcp4_unittests-decline_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='decline_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-decline_unittest.o `test -f 'decline_unittest.cc' || echo '$(srcdir)/'`decline_unittest.cc
+
+dhcp4_unittests-decline_unittest.obj: decline_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-decline_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-decline_unittest.Tpo -c -o dhcp4_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)/dhcp4_unittests-decline_unittest.Tpo $(DEPDIR)/dhcp4_unittests-decline_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='decline_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-decline_unittest.obj `if test -f 'decline_unittest.cc'; then $(CYGPATH_W) 'decline_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/decline_unittest.cc'; fi`
+
+dhcp4_unittests-kea_controller_unittest.o: kea_controller_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-kea_controller_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-kea_controller_unittest.Tpo -c -o dhcp4_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)/dhcp4_unittests-kea_controller_unittest.Tpo $(DEPDIR)/dhcp4_unittests-kea_controller_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kea_controller_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-kea_controller_unittest.o `test -f 'kea_controller_unittest.cc' || echo '$(srcdir)/'`kea_controller_unittest.cc
+
+dhcp4_unittests-kea_controller_unittest.obj: kea_controller_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-kea_controller_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-kea_controller_unittest.Tpo -c -o dhcp4_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)/dhcp4_unittests-kea_controller_unittest.Tpo $(DEPDIR)/dhcp4_unittests-kea_controller_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kea_controller_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_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`
+
+dhcp4_unittests-dhcp4to6_ipc_unittest.o: dhcp4to6_ipc_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-dhcp4to6_ipc_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-dhcp4to6_ipc_unittest.Tpo -c -o dhcp4_unittests-dhcp4to6_ipc_unittest.o `test -f 'dhcp4to6_ipc_unittest.cc' || echo '$(srcdir)/'`dhcp4to6_ipc_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-dhcp4to6_ipc_unittest.Tpo $(DEPDIR)/dhcp4_unittests-dhcp4to6_ipc_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp4to6_ipc_unittest.cc' object='dhcp4_unittests-dhcp4to6_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-dhcp4to6_ipc_unittest.o `test -f 'dhcp4to6_ipc_unittest.cc' || echo '$(srcdir)/'`dhcp4to6_ipc_unittest.cc
+
+dhcp4_unittests-dhcp4to6_ipc_unittest.obj: dhcp4to6_ipc_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-dhcp4to6_ipc_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-dhcp4to6_ipc_unittest.Tpo -c -o dhcp4_unittests-dhcp4to6_ipc_unittest.obj `if test -f 'dhcp4to6_ipc_unittest.cc'; then $(CYGPATH_W) 'dhcp4to6_ipc_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp4to6_ipc_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-dhcp4to6_ipc_unittest.Tpo $(DEPDIR)/dhcp4_unittests-dhcp4to6_ipc_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp4to6_ipc_unittest.cc' object='dhcp4_unittests-dhcp4to6_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-dhcp4to6_ipc_unittest.obj `if test -f 'dhcp4to6_ipc_unittest.cc'; then $(CYGPATH_W) 'dhcp4to6_ipc_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp4to6_ipc_unittest.cc'; fi`
+
+dhcp4_unittests-simple_parser4_unittest.o: simple_parser4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-simple_parser4_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-simple_parser4_unittest.Tpo -c -o dhcp4_unittests-simple_parser4_unittest.o `test -f 'simple_parser4_unittest.cc' || echo '$(srcdir)/'`simple_parser4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-simple_parser4_unittest.Tpo $(DEPDIR)/dhcp4_unittests-simple_parser4_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='simple_parser4_unittest.cc' object='dhcp4_unittests-simple_parser4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-simple_parser4_unittest.o `test -f 'simple_parser4_unittest.cc' || echo '$(srcdir)/'`simple_parser4_unittest.cc
+
+dhcp4_unittests-simple_parser4_unittest.obj: simple_parser4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-simple_parser4_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-simple_parser4_unittest.Tpo -c -o dhcp4_unittests-simple_parser4_unittest.obj `if test -f 'simple_parser4_unittest.cc'; then $(CYGPATH_W) 'simple_parser4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/simple_parser4_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-simple_parser4_unittest.Tpo $(DEPDIR)/dhcp4_unittests-simple_parser4_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='simple_parser4_unittest.cc' object='dhcp4_unittests-simple_parser4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-simple_parser4_unittest.obj `if test -f 'simple_parser4_unittest.cc'; then $(CYGPATH_W) 'simple_parser4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/simple_parser4_unittest.cc'; fi`
+
+dhcp4_unittests-get_config_unittest.o: get_config_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-get_config_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-get_config_unittest.Tpo -c -o dhcp4_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)/dhcp4_unittests-get_config_unittest.Tpo $(DEPDIR)/dhcp4_unittests-get_config_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='get_config_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-get_config_unittest.o `test -f 'get_config_unittest.cc' || echo '$(srcdir)/'`get_config_unittest.cc
+
+dhcp4_unittests-get_config_unittest.obj: get_config_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-get_config_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-get_config_unittest.Tpo -c -o dhcp4_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)/dhcp4_unittests-get_config_unittest.Tpo $(DEPDIR)/dhcp4_unittests-get_config_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='get_config_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_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`
+
+dhcp4_unittests-shared_network_unittest.o: shared_network_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-shared_network_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-shared_network_unittest.Tpo -c -o dhcp4_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)/dhcp4_unittests-shared_network_unittest.Tpo $(DEPDIR)/dhcp4_unittests-shared_network_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='shared_network_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-shared_network_unittest.o `test -f 'shared_network_unittest.cc' || echo '$(srcdir)/'`shared_network_unittest.cc
+
+dhcp4_unittests-shared_network_unittest.obj: shared_network_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-shared_network_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-shared_network_unittest.Tpo -c -o dhcp4_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)/dhcp4_unittests-shared_network_unittest.Tpo $(DEPDIR)/dhcp4_unittests-shared_network_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='shared_network_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_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`
+
+dhcp4_unittests-host_unittest.o: host_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-host_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-host_unittest.Tpo -c -o dhcp4_unittests-host_unittest.o `test -f 'host_unittest.cc' || echo '$(srcdir)/'`host_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp4_unittests-host_unittest.Tpo $(DEPDIR)/dhcp4_unittests-host_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-host_unittest.o `test -f 'host_unittest.cc' || echo '$(srcdir)/'`host_unittest.cc
+
+dhcp4_unittests-host_unittest.obj: host_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-host_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-host_unittest.Tpo -c -o dhcp4_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)/dhcp4_unittests-host_unittest.Tpo $(DEPDIR)/dhcp4_unittests-host_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-host_unittest.obj `if test -f 'host_unittest.cc'; then $(CYGPATH_W) 'host_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/host_unittest.cc'; fi`
+
+dhcp4_unittests-vendor_opts_unittest.o: vendor_opts_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-vendor_opts_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-vendor_opts_unittest.Tpo -c -o dhcp4_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)/dhcp4_unittests-vendor_opts_unittest.Tpo $(DEPDIR)/dhcp4_unittests-vendor_opts_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='vendor_opts_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-vendor_opts_unittest.o `test -f 'vendor_opts_unittest.cc' || echo '$(srcdir)/'`vendor_opts_unittest.cc
+
+dhcp4_unittests-vendor_opts_unittest.obj: vendor_opts_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-vendor_opts_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-vendor_opts_unittest.Tpo -c -o dhcp4_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)/dhcp4_unittests-vendor_opts_unittest.Tpo $(DEPDIR)/dhcp4_unittests-vendor_opts_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='vendor_opts_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_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`
+
+dhcp4_unittests-client_handler_unittest.o: client_handler_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-client_handler_unittest.o -MD -MP -MF $(DEPDIR)/dhcp4_unittests-client_handler_unittest.Tpo -c -o dhcp4_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)/dhcp4_unittests-client_handler_unittest.Tpo $(DEPDIR)/dhcp4_unittests-client_handler_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='client_handler_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_unittests-client_handler_unittest.o `test -f 'client_handler_unittest.cc' || echo '$(srcdir)/'`client_handler_unittest.cc
+
+dhcp4_unittests-client_handler_unittest.obj: client_handler_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp4_unittests-client_handler_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp4_unittests-client_handler_unittest.Tpo -c -o dhcp4_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)/dhcp4_unittests-client_handler_unittest.Tpo $(DEPDIR)/dhcp4_unittests-client_handler_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='client_handler_unittest.cc' object='dhcp4_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) $(dhcp4_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp4_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`
+
+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)/dhcp4_unittests-classify_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-client_handler_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-config_backend_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-config_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-ctrl_dhcp4_srv_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-d2_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-decline_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-dhcp4_client.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-dhcp4_srv_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-dhcp4_test_utils.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-dhcp4_unittests.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-dhcp4to6_ipc_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-direct_client_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-dora_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-fqdn_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-get_config_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-hooks_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-host_options_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-host_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-inform_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-kea_controller_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-marker_file.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-out_of_range_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-parser_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-release_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-shared_network_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-simple_parser4_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_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)/dhcp4_unittests-classify_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-client_handler_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-config_backend_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-config_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-ctrl_dhcp4_srv_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-d2_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-decline_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-dhcp4_client.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-dhcp4_srv_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-dhcp4_test_utils.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-dhcp4_unittests.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-dhcp4to6_ipc_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-direct_client_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-dora_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-fqdn_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-get_config_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-hooks_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-host_options_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-host_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-inform_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-kea_controller_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-marker_file.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-out_of_range_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-parser_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-release_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-shared_network_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_unittests-simple_parser4_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp4_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@ ./dhcp4_unittests --gtest_filter="Dhcp4Parser*" > /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@ ./dhcp4_unittests --gtest_filter="Dhcp4GetConfig*" > /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/dhcp4/tests/callout_library_1.cc b/src/bin/dhcp4/tests/callout_library_1.cc
new file mode 100644
index 0000000..73e3a17
--- /dev/null
+++ b/src/bin/dhcp4/tests/callout_library_1.cc
@@ -0,0 +1,30 @@
+// Copyright (C) 2013-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/.
+
+/// @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 <dhcp4/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" {
+
+/// @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/dhcp4/tests/callout_library_2.cc b/src/bin/dhcp4/tests/callout_library_2.cc
new file mode 100644
index 0000000..7b1ad4b
--- /dev/null
+++ b/src/bin/dhcp4/tests/callout_library_2.cc
@@ -0,0 +1,16 @@
+// Copyright (C) 2013-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/.
+
+/// @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 <dhcp4/tests/callout_library_common.h>
diff --git a/src/bin/dhcp4/tests/callout_library_3.cc b/src/bin/dhcp4/tests/callout_library_3.cc
new file mode 100644
index 0000000..494b258
--- /dev/null
+++ b/src/bin/dhcp4/tests/callout_library_3.cc
@@ -0,0 +1,78 @@
+// Copyright (C) 2018-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/.
+
+/// @file
+/// @brief Callout library for testing execution of the dhcp4_srv_configured
+/// hook point.
+///
+static const int LIBRARY_NUMBER = 3;
+
+#include <config.h>
+
+#include <dhcp4/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" {
+
+/// @brief Callout which appends library number and provided arguments to
+/// the marker file for dhcp4_srv_configured callout.
+///
+/// @param handle callout handle passed to the callout.
+///
+/// @return 0 on success, 1 otherwise.
+int
+dhcp4_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("Dhcp4/hooks-libraries") ?
+ config->toElement()->find("Dhcp4/hooks-libraries")->get(0) ?
+ config->toElement()->find("Dhcp4/hooks-libraries")->get(0)->get("parameters") ?
+ config->toElement()->find("Dhcp4/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/dhcp4/tests/callout_library_common.h b/src/bin/dhcp4/tests/callout_library_common.h
new file mode 100644
index 0000000..568746a
--- /dev/null
+++ b/src/bin/dhcp4/tests/callout_library_common.h
@@ -0,0 +1,96 @@
+// 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/.
+
+/// @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 dhcp4_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" {
+
+/// @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&) {
+ return (appendDigit(LOAD_MARKER_FILE));
+}
+
+int
+unload() {
+ return (appendDigit(UNLOAD_MARKER_FILE));
+}
+
+};
diff --git a/src/bin/dhcp4/tests/classify_unittest.cc b/src/bin/dhcp4/tests/classify_unittest.cc
new file mode 100644
index 0000000..2d06a49
--- /dev/null
+++ b/src/bin/dhcp4/tests/classify_unittest.cc
@@ -0,0 +1,1443 @@
+// Copyright (C) 2016-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 <asiolink/io_address.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp4/tests/dhcp4_client.h>
+#include <dhcp/option_int.h>
+#include <stats/stats_mgr.h>
+#include <algorithm>
+#include <vector>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace std;
+
+namespace {
+
+/// @brief Set of JSON configurations used throughout the classify tests.
+///
+/// - Configuration 0:
+/// - Used for testing dynamic assignment of client classes
+/// - 1 subnet: 10.0.0.0/24
+/// - 1 pool: 10.0.0.10-10.0.0.100
+/// - the following classes defined:
+/// option[93].hex == 0x0009, next-server set to 1.2.3.4
+/// option[93].hex == 0x0007, set server-hostname to deneb
+/// option[93].hex == 0x0006, set boot-file-name to pxelinux.0
+/// option[93].hex == 0x0001, set boot-file-name to ipxe.efi
+///
+/// - Configuration 1:
+/// - Used for testing reservations of client classes for a client
+/// - The following classes are defined:
+/// - 'pxe', next-server set to 1.2.3.4, assigned dynamically
+/// - 'reserved-class1', routers set to 10.0.0.200, reserved for a
+/// host using HW address 'aa:bb:cc:dd:ee:ff'
+/// - 'reserved-class2', domain-name-servers set to 10.0.0.201,
+/// also reserved for the host using HW address
+/// 'aa:bb:cc:dd:ee:ff'
+/// - Subnet of 10.0.0.0/24 with a single address pool
+///
+/// - Configuration 2:
+/// - Used for testing client class combination
+/// - 1 subnet: 10.0.0.0/24
+/// - 1 pool: 10.0.0.10-10.0.0.100
+/// - the following classes defined:
+/// not (option[93].hex == 0x0009)
+/// not member(<preceeding>), next-server set to 1.2.3.4
+/// option[93].hex == 0x0006
+/// option[93].hex == 0x0001
+/// or member(<last two>), set boot-file-name to pxelinux.0
+///
+/// - Configuration 3:
+/// - Used for required classification
+/// - 1 subnet: 10.0.0.0/24
+/// - 1 pool: 10.0.0.10-10.0.0.100
+/// - the following classes defined:
+/// option[93].hex == 0x0009, next-server set to 1.2.3.4
+/// option[93].hex == 0x0007, set server-hostname to deneb
+/// option[93].hex == 0x0006, set boot-file-name to pxelinux.0
+/// option[93].hex == 0x0001, set boot-file-name to ipxe.efi
+///
+/// - Configuration 4:
+/// - Used for complex membership (example taken from HA)
+/// - 1 subnet: 10.0.0.0/24
+/// - 4 pools: 10.0.0.10-10.0.0.49, 10.0.0.60-10.0.0.99,
+/// 10.0.0.110-10.0.0.149, 10.0.0.1.60-10.0.0.199
+/// - 4 classes to compose:
+/// server1 and server2 for each HA server
+/// option[93].hex == 0x0009 aka telephones
+/// option[93].hex == 0x0007 aka computers
+///
+/// - Configuration 5:
+/// - Used for the DROP class
+/// - 1 subnet: 10.0.0.0/24
+/// - 1 pool: 10.0.0.10-10.0.0.100
+/// - the following class defined: option[93].hex == 0x0009, DROP
+///
+/// - Configuration 6:
+/// - Used for the DROP class and reservation existence.
+/// - 1 subnet: 10.0.0.0/24
+/// - 1 pool: 10.0.0.10-10.0.0.100
+/// - 1 reservation for HW address 'aa:bb:cc:dd:ee:ff'
+/// - the following class defined: not member('KNOWN'), DROP
+/// (the not member also verifies that the DROP class is set only
+/// after the host reservation lookup)
+/// @note the reservation includes a hostname because raw reservations are
+/// not yet allowed.
+///
+/// - Configuration 7:
+/// - Used for the DROP class and reservation class.
+/// - 1 subnet: 10.0.0.0/24
+/// - 1 pool: 10.0.0.10-10.0.0.100
+/// - 1 reservation for HW address 'aa:bb:cc:dd:ee:ff'
+/// setting the allowed class
+/// - the following classes 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 8:
+/// - Used for the early global reservations lookup / select subnet.
+/// - 2 subnets: 10.0.0.0/24 guarded by first and 10.0.1.0/24
+/// - 2 pools: 10.0.0.10-10.0.0.100 and 10.0.1.10-10.0.1.100
+/// - 1 global reservation for HW address 'aa:bb:cc:dd:ee:ff'
+/// setting the first class
+/// - the following class defined: first
+///
+/// - Configuration 9:
+/// - Used for the early global reservations lookup / drop.
+/// - 1 subnet: 10.0.0.0/24
+/// - 1 pool: 10.0.0.10-10.0.0.100
+/// - 1 reservation for HW address 'aa:bb:cc:dd:ee:ff'
+/// setting the DROP class
+///
+const char* CONFIGS[] = {
+ // Configuration 0
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"pxe1\","
+ " \"test\": \"option[93].hex == 0x0009\","
+ " \"next-server\": \"1.2.3.4\""
+ "},"
+ "{"
+ " \"name\": \"pxe2\","
+ " \"test\": \"option[93].hex == 0x0007\","
+ " \"server-hostname\": \"deneb\""
+ "},"
+ "{"
+ " \"name\": \"pxe3\","
+ " \"test\": \"option[93].hex == 0x0006\","
+ " \"boot-file-name\": \"pxelinux.0\""
+ "},"
+ "{"
+ " \"name\": \"pxe4\","
+ " \"test\": \"option[93].hex == 0x0001\","
+ " \"boot-file-name\": \"ipxe.efi\""
+ "}],"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ]"
+ " } ]"
+ "}",
+
+ // Configuration 1
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"pxe\","
+ " \"test\": \"option[93].hex == 0x0009\","
+ " \"next-server\": \"1.2.3.4\""
+ "},"
+ "{"
+ " \"name\": \"reserved-class1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"routers\","
+ " \"data\": \"10.0.0.200\""
+ " }"
+ " ]"
+ "},"
+ "{"
+ " \"name\": \"reserved-class2\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.201\""
+ " }"
+ " ]"
+ "}"
+ "],"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"reservations\": [ "
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"client-classes\": [ \"reserved-class1\", \"reserved-class2\" ]"
+ " }"
+ " ]"
+ " } ]"
+ "}",
+
+ // Configuration 2
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"not-pxe1\","
+ " \"test\": \"not (option[93].hex == 0x0009)\""
+ "},"
+ "{"
+ " \"name\": \"pxe1\","
+ " \"test\": \"not member('not-pxe1')\","
+ " \"next-server\": \"1.2.3.4\""
+ "},"
+ "{"
+ " \"name\": \"pxe3\","
+ " \"test\": \"option[93].hex == 0x0006\""
+ "},"
+ "{"
+ " \"name\": \"pxe4\","
+ " \"test\": \"option[93].hex == 0x0001\""
+ "},"
+ "{"
+ " \"name\": \"pxe34\","
+ " \"test\": \"member('pxe3') or member('pxe4')\","
+ " \"boot-file-name\": \"pxelinux.0\""
+ "}],"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ]"
+ " } ]"
+ "}",
+
+ // Configuration 3
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"pxe1\","
+ " \"test\": \"option[93].hex == 0x0009\","
+ " \"only-if-required\": true,"
+ " \"next-server\": \"1.2.3.4\""
+ "},"
+ "{"
+ " \"name\": \"pxe2\","
+ " \"test\": \"option[93].hex == 0x0007\","
+ " \"only-if-required\": true,"
+ " \"server-hostname\": \"deneb\""
+ "},"
+ "{"
+ " \"name\": \"pxe3\","
+ " \"test\": \"option[93].hex == 0x0006\","
+ " \"only-if-required\": false,"
+ " \"boot-file-name\": \"pxelinux.0\""
+ "},"
+ "{"
+ " \"name\": \"pxe4\","
+ " \"test\": \"option[93].hex == 0x0001\","
+ " \"boot-file-name\": \"ipxe.efi\""
+ "}],"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"require-client-classes\": [ \"pxe2\" ]"
+ " } ]"
+ "}",
+
+ // Configuration 4
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"server1\""
+ "},"
+ "{"
+ " \"name\": \"server2\""
+ "},"
+ "{"
+ " \"name\": \"telephones\","
+ " \"test\": \"option[93].hex == 0x0009\""
+ "},"
+ "{"
+ " \"name\": \"computers\","
+ " \"test\": \"option[93].hex == 0x0007\""
+ "},"
+ "{"
+ " \"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')\""
+ "} ],"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ "
+ " { \"pool\": \"10.0.0.10-10.0.0.49\","
+ " \"client-class\": \"server1_and_telephones\" },"
+ " { \"pool\": \"10.0.0.60-10.0.0.99\","
+ " \"client-class\": \"server1_and_computers\" },"
+ " { \"pool\": \"10.0.0.110-10.0.0.149\","
+ " \"client-class\": \"server2_and_telephones\" },"
+ " { \"pool\": \"10.0.0.160-10.0.0.199\","
+ " \"client-class\": \"server2_and_computers\" } ]"
+ " } ]"
+ "}",
+
+ // Configuration 5
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"DROP\","
+ " \"test\": \"option[93].hex == 0x0009\""
+ "}],"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ]"
+ " } ]"
+ "}",
+
+ // Configuration 6
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"DROP\","
+ " \"test\": \"not member('KNOWN')\""
+ "}],"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"reservations\": [ {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"hostname\": \"allowed\" } ]"
+ " } ]"
+ "}",
+
+ // Configuration 7
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"allowed\""
+ "},"
+ "{"
+ " \"name\": \"t\","
+ " \"test\": \"member('KNOWN') or member('UNKNOWN')\""
+ "},"
+ "{"
+ " \"name\": \"DROP\","
+ " \"test\": \"not member('allowed') and member('t')\""
+ "}],"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"reservations\": [ {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"client-classes\": [ \"allowed\" ] } ]"
+ " } ]"
+ "}",
+
+ // Configuration 8
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"early-global-reservations-lookup\": true,"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"first\""
+ "}],"
+ "\"subnet4\": ["
+ "{"
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 1,"
+ " \"interface\": \"eth0\","
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"client-class\": \"first\""
+ "},"
+ "{"
+ " \"subnet\": \"10.0.1.0/24\","
+ " \"interface\": \"eth0\","
+ " \"id\": 2,"
+ " \"pools\": [ { \"pool\": \"10.0.1.10-10.0.1.100\" } ]"
+ "}],"
+ "\"reservations\": [ {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"client-classes\": [ \"first\" ] } ]"
+ "}",
+
+ // Configuration 9
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"early-global-reservations-lookup\": true,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"interface\": \"eth0\","
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ] } ],"
+ "\"reservations\": [ {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"client-classes\": [ \"DROP\" ] } ]"
+ "}",
+
+};
+
+/// @brief Test fixture class for testing classification.
+///
+/// For the time being it covers only fixed fields, but it's going to be
+/// expanded to cover other cases.
+class ClassifyTest : public Dhcpv4SrvTest {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets up fake interfaces.
+ ClassifyTest()
+ : Dhcpv4SrvTest(),
+ iface_mgr_test_config_(true) {
+ }
+
+ /// @brief Destructor.
+ ///
+ ~ClassifyTest() {
+ }
+
+ /// @brief Does client exchanges and checks if fixed fields have expected values.
+ ///
+ /// Depending on the value of msgtype (allowed types: DHCPDISCOVER, DHCPREQUEST or
+ /// DHCPINFORM), this method sets up the server, then conducts specified exchange
+ /// and then checks if the response contains expected values of next-server, sname
+ /// and filename fields.
+ ///
+ /// @param config server configuration to be used
+ /// @param msgtype DHCPDISCOVER, DHCPREQUEST or DHCPINFORM
+ /// @param extra_opt option to include in client messages (optional)
+ /// @param exp_next_server expected value of the next-server field
+ /// @param exp_sname expected value of the sname field
+ /// @param exp_filename expected value of the filename field
+ void
+ testFixedFields(const char* config, uint8_t msgtype, const OptionPtr& extra_opt,
+ const std::string& exp_next_server, const std::string& exp_sname,
+ const std::string& exp_filename) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Configure DHCP server.
+ configure(config, *client.getServer());
+
+ if (extra_opt) {
+ client.addExtraOption(extra_opt);
+ }
+
+ switch (msgtype) {
+ case DHCPDISCOVER:
+ client.doDiscover();
+ break;
+ case DHCPREQUEST:
+ client.doDORA();
+ break;
+ case DHCPINFORM:
+ // Preconfigure the client with the IP address.
+ client.createLease(IOAddress("10.0.0.56"), 600);
+
+ client.doInform(false);
+ break;
+ }
+
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+
+ EXPECT_EQ(exp_next_server, resp->getSiaddr().toText());
+
+ // This is bizarre. If I use Pkt4::MAX_SNAME_LEN in the ASSERT_GE macro,
+ // the linker will complain about it being not defined.
+ const size_t max_sname = Pkt4::MAX_SNAME_LEN;
+
+ ASSERT_GE(max_sname, exp_sname.length());
+ vector<uint8_t> sname(max_sname, 0);
+ memcpy(&sname[0], &exp_sname[0], exp_sname.size());
+ EXPECT_TRUE(std::equal(sname.begin(), sname.end(),
+ resp->getSname().begin()));
+
+ const size_t max_filename = Pkt4::MAX_FILE_LEN;
+ ASSERT_GE(max_filename, exp_filename.length());
+ vector<uint8_t> filename(max_filename, 0);
+ memcpy(&filename[0], &exp_filename[0], exp_filename.size());
+ EXPECT_TRUE(std::equal(filename.begin(), filename.end(),
+ resp->getFile().begin()));
+ }
+
+ /// @brief Interface Manager's fake configuration control.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+};
+
+
+// This test checks that an incoming DISCOVER that does not match any classes
+// will get the fixed fields empty.
+TEST_F(ClassifyTest, fixedFieldsDiscoverNoClasses) {
+ testFixedFields(CONFIGS[0], DHCPDISCOVER, OptionPtr(), "0.0.0.0", "", "");
+}
+// This test checks that an incoming REQUEST that does not match any classes
+// will get the fixed fields empty.
+TEST_F(ClassifyTest, fixedFieldsRequestNoClasses) {
+ testFixedFields(CONFIGS[0], DHCPREQUEST, OptionPtr(), "0.0.0.0", "", "");
+}
+// This test checks that an incoming INFORM that does not match any classes
+// will get the fixed fields empty.
+TEST_F(ClassifyTest, fixedFieldsInformNoClasses) {
+ testFixedFields(CONFIGS[0], DHCPINFORM, OptionPtr(), "0.0.0.0", "", "");
+}
+
+
+// This test checks that an incoming DISCOVER that does match a class that has
+// next-server specified will result in a response that has the next-server set.
+TEST_F(ClassifyTest, fixedFieldsDiscoverNextServer) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0009));
+
+ testFixedFields(CONFIGS[0], DHCPDISCOVER, pxe, "1.2.3.4", "", "");
+}
+// This test checks that an incoming REQUEST that does match a class that has
+// next-server specified will result in a response that has the next-server set.
+TEST_F(ClassifyTest, fixedFieldsRequestNextServer) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0009));
+
+ testFixedFields(CONFIGS[0], DHCPREQUEST, pxe, "1.2.3.4", "", "");
+}
+// This test checks that an incoming INFORM that does match a class that has
+// next-server specified will result in a response that has the next-server set.
+TEST_F(ClassifyTest, fixedFieldsInformNextServer) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0009));
+
+ testFixedFields(CONFIGS[0], DHCPINFORM, pxe, "1.2.3.4", "", "");
+}
+
+
+// This test checks that an incoming DISCOVER that does match a class that has
+// server-hostname specified will result in a response that has the sname field set.
+TEST_F(ClassifyTest, fixedFieldsDiscoverHostname) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0007));
+
+ testFixedFields(CONFIGS[0], DHCPDISCOVER, pxe, "0.0.0.0", "deneb", "");
+}
+// This test checks that an incoming REQUEST that does match a class that has
+// server-hostname specified will result in a response that has the sname field set.
+TEST_F(ClassifyTest, fixedFieldsRequestHostname) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0007));
+
+ testFixedFields(CONFIGS[0], DHCPREQUEST, pxe, "0.0.0.0", "deneb", "");
+}
+// This test checks that an incoming INFORM that does match a class that has
+// server-hostname specified will result in a response that has the sname field set.
+TEST_F(ClassifyTest, fixedFieldsInformHostname) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0007));
+
+ testFixedFields(CONFIGS[0], DHCPINFORM, pxe, "0.0.0.0", "deneb", "");
+}
+
+
+// This test checks that an incoming DISCOVER that does match a class that has
+// boot-file-name specified will result in a response that has the filename field set.
+TEST_F(ClassifyTest, fixedFieldsDiscoverFile1) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0006));
+
+ testFixedFields(CONFIGS[0], DHCPDISCOVER, pxe, "0.0.0.0", "", "pxelinux.0");
+}
+// This test checks that an incoming REQUEST that does match a class that has
+// boot-file-name specified will result in a response that has the filename field set.
+TEST_F(ClassifyTest, fixedFieldsRequestFile1) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0006));
+
+ testFixedFields(CONFIGS[0], DHCPREQUEST, pxe, "0.0.0.0", "", "pxelinux.0");
+}
+// This test checks that an incoming INFORM that does match a class that has
+// boot-file-name specified will result in a response that has the filename field set.
+TEST_F(ClassifyTest, fixedFieldsInformFile1) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0006));
+
+ testFixedFields(CONFIGS[0], DHCPDISCOVER, pxe, "0.0.0.0", "", "pxelinux.0");
+}
+
+
+// This test checks that an incoming DISCOVER that does match a different class that has
+// boot-file-name specified will result in a response that has the filename field set.
+TEST_F(ClassifyTest, fixedFieldsDiscoverFile2) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0001));
+
+ testFixedFields(CONFIGS[0], DHCPDISCOVER, pxe, "0.0.0.0", "", "ipxe.efi");
+}
+// This test checks that an incoming REQUEST that does match a different class that has
+// boot-file-name specified will result in a response that has the filename field set.
+TEST_F(ClassifyTest, fixedFieldsRequestFile2) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0001));
+
+ testFixedFields(CONFIGS[0], DHCPREQUEST, pxe, "0.0.0.0", "", "ipxe.efi");
+}
+// This test checks that an incoming INFORM that does match a different class that has
+// boot-file-name specified will result in a response that has the filename field set.
+TEST_F(ClassifyTest, fixedFieldsInformFile2) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0001));
+
+ testFixedFields(CONFIGS[0], DHCPINFORM, pxe, "0.0.0.0", "", "ipxe.efi");
+}
+
+// This test checks that it is possible to specify static reservations for
+// client classes.
+TEST_F(ClassifyTest, clientClassesInHostReservations) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Initially, the client uses hardware address for which there are
+ // no reservations.
+ client.setHWAddress("aa:bb:cc:dd:ee:fe");
+ // DNS servers have to be requested to be returned.
+ client.requestOptions(DHO_DOMAIN_NAME_SERVERS);
+
+ // Add option 93 that matches 'pxe' class in the configuration.
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0009));
+ client.addExtraOption(pxe);
+
+ // Configure DHCP server.
+ configure(CONFIGS[1], *client.getServer());
+
+ // Perform 4-way exchange. The client's HW address doesn't match the
+ // reservations, so we expect that only 'pxe' class will be matched.
+ ASSERT_NO_THROW(client.doDORA());
+
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+
+ // 'pxe' class matches so the siaddr should be set appropriately.
+ EXPECT_EQ("1.2.3.4", resp->getSiaddr().toText());
+ // This client has no reservations for the classes associated with
+ // DNS servers and Routers options.
+ EXPECT_EQ(0, client.config_.routers_.size());
+ EXPECT_EQ(0, client.config_.dns_servers_.size());
+
+ // Modify HW address to match the reservations.
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+ ASSERT_NO_THROW(client.doDORA());
+
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+
+ // This time, the client matches 3 classes (for two it has reservations).
+ EXPECT_EQ("1.2.3.4", resp->getSiaddr().toText());
+ EXPECT_EQ(1, client.config_.routers_.size());
+ EXPECT_EQ("10.0.0.200", client.config_.routers_[0].toText());
+ EXPECT_EQ(1, client.config_.dns_servers_.size());
+ EXPECT_EQ("10.0.0.201", client.config_.dns_servers_[0].toText());
+
+ // This should also work for DHCPINFORM case.
+ ASSERT_NO_THROW(client.doInform());
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+
+ EXPECT_EQ("1.2.3.4", resp->getSiaddr().toText());
+ EXPECT_EQ(1, client.config_.routers_.size());
+ EXPECT_EQ("10.0.0.200", client.config_.routers_[0].toText());
+ EXPECT_EQ(1, client.config_.dns_servers_.size());
+ EXPECT_EQ("10.0.0.201", client.config_.dns_servers_[0].toText());
+}
+
+// This test checks that an incoming DISCOVER that does not match any classes
+// will get the fixed fields empty.
+TEST_F(ClassifyTest, fixedFieldsDiscoverNoClasses2) {
+ testFixedFields(CONFIGS[2], DHCPDISCOVER, OptionPtr(), "0.0.0.0", "", "");
+}
+// This test checks that an incoming REQUEST that does not match any classes
+// will get the fixed fields empty.
+TEST_F(ClassifyTest, fixedFieldsRequestNoClasses2) {
+ testFixedFields(CONFIGS[2], DHCPREQUEST, OptionPtr(), "0.0.0.0", "", "");
+}
+// This test checks that an incoming INFORM that does not match any classes
+// will get the fixed fields empty.
+TEST_F(ClassifyTest, fixedFieldsInformNoClasses2) {
+ testFixedFields(CONFIGS[2], DHCPINFORM, OptionPtr(), "0.0.0.0", "", "");
+}
+
+
+// This test checks that an incoming DISCOVER that does match a class that has
+// next-server specified will result in a response that has the next-server set.
+TEST_F(ClassifyTest, fixedFieldsDiscoverNextServer2) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0009));
+
+ testFixedFields(CONFIGS[2], DHCPDISCOVER, pxe, "1.2.3.4", "", "");
+}
+// This test checks that an incoming REQUEST that does match a class that has
+// next-server specified will result in a response that has the next-server set.
+TEST_F(ClassifyTest, fixedFieldsRequestNextServer2) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0009));
+
+ testFixedFields(CONFIGS[2], DHCPREQUEST, pxe, "1.2.3.4", "", "");
+}
+// This test checks that an incoming INFORM that does match a class that has
+// next-server specified will result in a response that has the next-server set.
+TEST_F(ClassifyTest, fixedFieldsInformNextServer2) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0009));
+
+ testFixedFields(CONFIGS[2], DHCPINFORM, pxe, "1.2.3.4", "", "");
+}
+
+
+// This test checks that an incoming DISCOVER that does match a class that has
+// boot-file-name specified will result in a response that has the filename field set.
+TEST_F(ClassifyTest, fixedFieldsDiscoverFile21) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0006));
+
+ testFixedFields(CONFIGS[2], DHCPDISCOVER, pxe, "0.0.0.0", "", "pxelinux.0");
+}
+// This test checks that an incoming REQUEST that does match a class that has
+// boot-file-name specified will result in a response that has the filename field set.
+TEST_F(ClassifyTest, fixedFieldsRequestFile21) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0006));
+
+ testFixedFields(CONFIGS[2], DHCPREQUEST, pxe, "0.0.0.0", "", "pxelinux.0");
+}
+// This test checks that an incoming INFORM that does match a class that has
+// boot-file-name specified will result in a response that has the filename field set.
+TEST_F(ClassifyTest, fixedFieldsInformFile21) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0006));
+
+ testFixedFields(CONFIGS[2], DHCPDISCOVER, pxe, "0.0.0.0", "", "pxelinux.0");
+}
+
+
+// This test checks that an incoming DISCOVER that does match a different class that has
+// boot-file-name specified will result in a response that has the filename field set.
+TEST_F(ClassifyTest, fixedFieldsDiscoverFile22) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0001));
+
+ testFixedFields(CONFIGS[2], DHCPDISCOVER, pxe, "0.0.0.0", "", "pxelinux.0");
+}
+// This test checks that an incoming REQUEST that does match a different class that has
+// boot-file-name specified will result in a response that has the filename field set.
+TEST_F(ClassifyTest, fixedFieldsRequestFile22) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0001));
+
+ testFixedFields(CONFIGS[2], DHCPREQUEST, pxe, "0.0.0.0", "", "pxelinux.0");
+}
+// This test checks that an incoming INFORM that does match a different class that has
+// boot-file-name specified will result in a response that has the filename field set.
+TEST_F(ClassifyTest, fixedFieldsInformFile22) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0001));
+
+ testFixedFields(CONFIGS[2], DHCPINFORM, pxe, "0.0.0.0", "", "pxelinux.0");
+}
+
+// No class
+TEST_F(ClassifyTest, fixedFieldsDiscoverNoClasses3) {
+ testFixedFields(CONFIGS[3], DHCPDISCOVER, OptionPtr(), "0.0.0.0", "", "");
+}
+TEST_F(ClassifyTest, fixedFieldsRequestNoClasses3) {
+ testFixedFields(CONFIGS[3], DHCPREQUEST, OptionPtr(), "0.0.0.0", "", "");
+}
+TEST_F(ClassifyTest, fixedFieldsInformNoClasses3) {
+ testFixedFields(CONFIGS[3], DHCPINFORM, OptionPtr(), "0.0.0.0", "", "");
+}
+
+// Class 'pxe1' is only-if-required and not subject to required evaluation
+TEST_F(ClassifyTest, fixedFieldsDiscoverNextServer3) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0009));
+
+ testFixedFields(CONFIGS[3], DHCPDISCOVER, pxe, "0.0.0.0", "", "");
+}
+TEST_F(ClassifyTest, fixedFieldsRequestNextServer3) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0009));
+
+ testFixedFields(CONFIGS[3], DHCPREQUEST, pxe, "0.0.0.0", "", "");
+}
+TEST_F(ClassifyTest, fixedFieldsInformNextServer3) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0009));
+
+ testFixedFields(CONFIGS[3], DHCPINFORM, pxe, "0.0.0.0", "", "");
+}
+
+
+// Class pxe2 is only-if-required but the subnet requires its evaluation
+TEST_F(ClassifyTest, fixedFieldsDiscoverHostname3) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0007));
+
+ testFixedFields(CONFIGS[3], DHCPDISCOVER, pxe, "0.0.0.0", "deneb", "");
+}
+TEST_F(ClassifyTest, fixedFieldsRequestHostname3) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0007));
+
+ testFixedFields(CONFIGS[3], DHCPREQUEST, pxe, "0.0.0.0", "deneb", "");
+}
+TEST_F(ClassifyTest, fixedFieldsInformHostname3) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0007));
+
+ testFixedFields(CONFIGS[3], DHCPINFORM, pxe, "0.0.0.0", "deneb", "");
+}
+
+// No change from config #0 for pxe3 and pxe4
+TEST_F(ClassifyTest, fixedFieldsDiscoverFile31) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0006));
+
+ testFixedFields(CONFIGS[3], DHCPDISCOVER, pxe, "0.0.0.0", "", "pxelinux.0");
+}
+TEST_F(ClassifyTest, fixedFieldsRequestFile31) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0006));
+
+ testFixedFields(CONFIGS[3], DHCPREQUEST, pxe, "0.0.0.0", "", "pxelinux.0");
+}
+TEST_F(ClassifyTest, fixedFieldsInformFile31) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0006));
+
+ testFixedFields(CONFIGS[3], DHCPDISCOVER, pxe, "0.0.0.0", "", "pxelinux.0");
+}
+TEST_F(ClassifyTest, fixedFieldsDiscoverFile32) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0001));
+
+ testFixedFields(CONFIGS[3], DHCPDISCOVER, pxe, "0.0.0.0", "", "ipxe.efi");
+}
+TEST_F(ClassifyTest, fixedFieldsRequestFile32) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0001));
+
+ testFixedFields(CONFIGS[3], DHCPREQUEST, pxe, "0.0.0.0", "", "ipxe.efi");
+}
+TEST_F(ClassifyTest, fixedFieldsInformFile32) {
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0001));
+
+ testFixedFields(CONFIGS[3], DHCPINFORM, pxe, "0.0.0.0", "", "ipxe.efi");
+}
+
+// This test checks the complex membership from HA with server1 telephone.
+TEST_F(ClassifyTest, server1Telephone) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Configure DHCP server.
+ configure(CONFIGS[4], *client.getServer());
+
+ // Add option.
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0009));
+ client.addExtraOption(pxe);
+
+ // Add server1
+ client.addClass("server1");
+
+ // Get an address
+ client.doDORA();
+
+ // Check response.
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The address is from the first pool.
+ EXPECT_EQ("10.0.0.10", resp->getYiaddr().toText());
+}
+
+// This test checks the complex membership from HA with server1 computer.
+TEST_F(ClassifyTest, server1computer) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Configure DHCP server.
+ configure(CONFIGS[4], *client.getServer());
+
+ // Add option.
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0007));
+ client.addExtraOption(pxe);
+
+ // Add server1
+ client.addClass("server1");
+
+ // Get an address
+ client.doDORA();
+
+ // Check response.
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The address is from the second pool.
+ EXPECT_EQ("10.0.0.60", resp->getYiaddr().toText());
+}
+
+// This test checks the complex membership from HA with server2 telephone.
+TEST_F(ClassifyTest, server2Telephone) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Configure DHCP server.
+ configure(CONFIGS[4], *client.getServer());
+
+ // Add option.
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0009));
+ client.addExtraOption(pxe);
+
+ // Add server2
+ client.addClass("server2");
+
+ // Get an address
+ client.doDORA();
+
+ // Check response.
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The address is from the third pool.
+ EXPECT_EQ("10.0.0.110", resp->getYiaddr().toText());
+}
+
+// This test checks the complex membership from HA with server2 computer.
+TEST_F(ClassifyTest, server2computer) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Configure DHCP server.
+ configure(CONFIGS[4], *client.getServer());
+
+ // Add option.
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0007));
+ client.addExtraOption(pxe);
+
+ // Add server2
+ client.addClass("server2");
+
+ // Get an address
+ client.doDORA();
+
+ // Check response.
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The address is from the forth pool.
+ EXPECT_EQ("10.0.0.160", resp->getYiaddr().toText());
+}
+
+// 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\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ " {"
+ " \"name\": \"all\","
+ " \"test\": \"'' == ''\""
+ " },"
+ " {"
+ " \"name\": \"for-pool\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.1\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-subnet\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.2\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-network\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.3\""
+ " } ]"
+ " }"
+ "],"
+ "\"shared-networks\": [ {"
+ " \"name\": \"frog\","
+ " \"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 1,"
+ " \"pools\": [ { "
+ " \"pool\": \"10.0.0.10-10.0.0.100\""
+ " } ]"
+ " } ]"
+ "} ]"
+ "}";
+
+ // Create a client requesting domain-name-servers option
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.requestOptions(DHO_DOMAIN_NAME_SERVERS);
+
+ // Load the config and perform a DORA
+ configure(config, *client.getServer());
+ ASSERT_NO_THROW(client.doDORA());
+
+ // Check response
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ EXPECT_EQ("10.0.0.10", resp->getYiaddr().toText());
+
+ // Check domain-name-servers option
+ OptionPtr opt = resp->getOption(DHO_DOMAIN_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\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ " {"
+ " \"name\": \"all\","
+ " \"test\": \"'' == ''\""
+ " },"
+ " {"
+ " \"name\": \"for-pool\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.1\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-subnet\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.2\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-network\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.3\""
+ " } ]"
+ " }"
+ "],"
+ "\"shared-networks\": [ {"
+ " \"name\": \"frog\","
+ " \"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 1,"
+ " \"pools\": [ { "
+ " \"pool\": \"10.0.0.10-10.0.0.100\","
+ " \"require-client-classes\": [ \"for-pool\" ]"
+ " } ]"
+ " } ]"
+ "} ]"
+ "}";
+
+ // Create a client requesting domain-name-servers option
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.requestOptions(DHO_DOMAIN_NAME_SERVERS);
+
+ // Load the config and perform a DORA
+ configure(config, *client.getServer());
+ ASSERT_NO_THROW(client.doDORA());
+
+ // Check response
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ EXPECT_EQ("10.0.0.10", resp->getYiaddr().toText());
+
+ // Check domain-name-servers option
+ OptionPtr opt = resp->getOption(DHO_DOMAIN_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option4AddrLstPtr servers =
+ boost::dynamic_pointer_cast<Option4AddrLst>(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("10.0.0.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\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ " {"
+ " \"name\": \"all\","
+ " \"test\": \"'' == ''\""
+ " },"
+ " {"
+ " \"name\": \"for-pool\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.1\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-subnet\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.2\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-network\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.3\""
+ " } ]"
+ " }"
+ "],"
+ "\"shared-networks\": [ {"
+ " \"name\": \"frog\","
+ " \"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 1,"
+ " \"require-client-classes\": [ \"for-subnet\" ],"
+ " \"pools\": [ { "
+ " \"pool\": \"10.0.0.10-10.0.0.100\","
+ " \"require-client-classes\": [ \"for-pool\" ]"
+ " } ]"
+ " } ]"
+ "} ]"
+ "}";
+
+ // Create a client requesting domain-name-servers option
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.requestOptions(DHO_DOMAIN_NAME_SERVERS);
+
+ // Load the config and perform a DORA
+ configure(config, *client.getServer());
+ ASSERT_NO_THROW(client.doDORA());
+
+ // Check response
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ EXPECT_EQ("10.0.0.10", resp->getYiaddr().toText());
+
+ // Check domain-name-servers option
+ OptionPtr opt = resp->getOption(DHO_DOMAIN_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option4AddrLstPtr servers =
+ boost::dynamic_pointer_cast<Option4AddrLst>(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("10.0.0.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\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ " {"
+ " \"name\": \"all\","
+ " \"test\": \"'' == ''\""
+ " },"
+ " {"
+ " \"name\": \"for-pool\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.1\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-subnet\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.2\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-network\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.3\""
+ " } ]"
+ " }"
+ "],"
+ "\"shared-networks\": [ {"
+ " \"name\": \"frog\","
+ " \"require-client-classes\": [ \"for-network\" ],"
+ " \"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 1,"
+ " \"require-client-classes\": [ \"for-subnet\" ],"
+ " \"pools\": [ { "
+ " \"pool\": \"10.0.0.10-10.0.0.100\","
+ " \"require-client-classes\": [ \"for-pool\" ]"
+ " } ]"
+ " } ]"
+ "} ]"
+ "}";
+
+ // Create a client requesting domain-name-servers option
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.requestOptions(DHO_DOMAIN_NAME_SERVERS);
+
+ // Load the config and perform a DORA
+ configure(config, *client.getServer());
+ ASSERT_NO_THROW(client.doDORA());
+
+ // Check response
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ EXPECT_EQ("10.0.0.10", resp->getYiaddr().toText());
+
+ // Check domain-name-servers option
+ OptionPtr opt = resp->getOption(DHO_DOMAIN_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option4AddrLstPtr servers =
+ boost::dynamic_pointer_cast<Option4AddrLst>(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("10.0.0.3", addrs[0].toText());
+}
+
+// This test checks the handling for the DROP special class.
+TEST_F(ClassifyTest, dropClass) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Configure DHCP server.
+ configure(CONFIGS[5], *client.getServer());
+
+ // Send the discover.
+ client.doDiscover();
+
+ // No option: no drop.
+ EXPECT_TRUE(client.getContext().response_);
+
+ // Retry with an option matching the DROP class.
+ Dhcp4Client client2(Dhcp4Client::SELECTING);
+
+ // Add the pxe option.
+ OptionPtr pxe(new OptionInt<uint16_t>(Option::V4, 93, 0x0009));
+ client2.addExtraOption(pxe);
+
+ // Send the discover.
+ client2.doDiscover();
+
+ // Option, dropped.
+ EXPECT_FALSE(client2.getContext().response_);
+
+ // There should also be pkt4-receive-drop stat bumped up.
+ stats::StatsMgr& mgr = stats::StatsMgr::instance();
+ stats::ObservationPtr drop_stat = mgr.getObservation("pkt4-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) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Configure DHCP server.
+ configure(CONFIGS[6], *client.getServer());
+
+ // Set the HW address to the reservation.
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+
+ // Send the discover.
+ client.doDiscover();
+
+ // Reservation match: no drop.
+ EXPECT_TRUE(client.getContext().response_);
+
+ // Retry with another HW address.
+ Dhcp4Client client2(Dhcp4Client::SELECTING);
+ client2.setHWAddress("aa:bb:cc:dd:ee:fe");
+
+ // Send the discover.
+ client2.doDiscover();
+
+ // No reservation, dropped.
+ EXPECT_FALSE(client2.getContext().response_);
+
+ // There should also be pkt4-receive-drop stat bumped up.
+ stats::StatsMgr& mgr = stats::StatsMgr::instance();
+ stats::ObservationPtr drop_stat = mgr.getObservation("pkt4-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) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Configure DHCP server.
+ configure(CONFIGS[7], *client.getServer());
+
+ // Set the HW address to the reservation.
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+
+ // Send the discover.
+ client.doDiscover();
+
+ // Reservation match: no drop.
+ EXPECT_TRUE(client.getContext().response_);
+
+ // Retry with another HW address.
+ Dhcp4Client client2(Dhcp4Client::SELECTING);
+ client2.setHWAddress("aa:bb:cc:dd:ee:fe");
+
+ // Send the discover.
+ client2.doDiscover();
+
+ // No reservation, dropped.
+ EXPECT_FALSE(client2.getContext().response_);
+
+ // There should also be pkt4-receive-drop stat bumped up.
+ stats::StatsMgr& mgr = stats::StatsMgr::instance();
+ stats::ObservationPtr drop_stat = mgr.getObservation("pkt4-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) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Configure DHCP server.
+ configure(CONFIGS[8], *client.getServer());
+
+ // Set the HW address to the reservation.
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+
+ // Send the discover.
+ client.doDiscover();
+
+ // Check response.
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ EXPECT_EQ("10.0.0.10", resp->getYiaddr().toText());
+
+ // Try with a different HW address.
+ Dhcp4Client client2(Dhcp4Client::SELECTING);
+
+ // Set the HW address to another value.
+ client2.setHWAddress("aa:bb:cc:01:ee:ff");
+
+ // Send the discover.
+ client2.doDiscover();
+
+ // Check response.
+ resp = client2.getContext().response_;
+ ASSERT_TRUE(resp);
+ EXPECT_EQ("10.0.1.10", resp->getYiaddr().toText());
+}
+
+// This test checks the early global reservations lookup for dropping.
+TEST_F(ClassifyTest, earlyDrop) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Configure DHCP server.
+ configure(CONFIGS[9], *client.getServer());
+
+ // Set the HW address to the reservation.
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+
+ // Send the discover.
+ client.doDiscover();
+
+ // Match the reservation so dropped.
+ EXPECT_FALSE(client.getContext().response_);
+
+ // There should also be pkt4-receive-drop stat bumped up.
+ stats::StatsMgr& mgr = stats::StatsMgr::instance();
+ stats::ObservationPtr drop_stat = mgr.getObservation("pkt4-receive-drop");
+
+ // This statistic must be present and must be set to 1.
+ ASSERT_TRUE(drop_stat);
+ EXPECT_EQ(1, drop_stat->getInteger().first);
+
+ // Try with a different HW address.
+ Dhcp4Client client2(Dhcp4Client::SELECTING);
+
+ // Set the HW address to another value.
+ client2.setHWAddress("aa:bb:cc:01:ee:ff");
+
+ // Send the discover.
+ client2.doDiscover();
+
+ // Not matching so not dropped.
+ EXPECT_TRUE(client2.getContext().response_);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/client_handler_unittest.cc b/src/bin/dhcp4/tests/client_handler_unittest.cc
new file mode 100644
index 0000000..50b3ba5
--- /dev/null
+++ b/src/bin/dhcp4/tests/client_handler_unittest.cc
@@ -0,0 +1,893 @@
+// 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 <dhcp4/client_handler.h>
+#include <dhcp4/tests/dhcp4_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 pkt4-receive-drop statistic.
+ ClientHandleTest() : called1_(false), called2_(false), called3_(false) {
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+ StatsMgr::instance().setValue("pkt4-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 dhcp4_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::V4, DHO_DHCP_CLIENT_IDENTIFIER, duid)));
+ }
+
+ /// @brief Generates a hardware address.
+ ///
+ /// (from dhcp4_test_utils.h)
+ ///
+ /// @return A random hardware address.
+ HWAddrPtr generateHWAddr(uint8_t base = 50) {
+ const uint16_t hw_type = 6;
+ const size_t len = 6;
+ OptionBuffer mac(len);
+ for (size_t i = 0; i < len; ++i) {
+ mac[i] = base + i;
+ }
+ return (HWAddrPtr(new HWAddr(mac, hw_type)));
+ }
+
+ /// @brief Check statistics.
+ ///
+ /// @param bumped True if pkt4-receive-drop should have been bumped by one,
+ /// false otherwise.
+ void checkStat(bool bumped) {
+ ObservationPtr obs = StatsMgr::instance().getObservation("pkt4-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.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ dis->addOption(generateClientId());
+ dis->setHWAddr(generateHWAddr());
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // 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 (id).
+TEST_F(ClientHandleTest, sharedQueriesById) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ OptionPtr client_id = generateClientId();
+ // Same client ID: same client.
+ dis->addOption(client_id);
+ req->addOption(client_id);
+ dis->setHWAddr(generateHWAddr());
+ req->setHWAddr(generateHWAddr(55));
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it with the discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // 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 two queries for the same client (hw).
+TEST_F(ClientHandleTest, sharedQueriesByHWAddr) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ dis->addOption(generateClientId());
+ req->addOption(generateClientId(111));
+ HWAddrPtr hwaddr = generateHWAddr();
+ // Same hardware address: same client.
+ dis->setHWAddr(hwaddr);
+ req->setHWAddr(hwaddr);
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it with the discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // 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 two queries for the same client (hw only).
+TEST_F(ClientHandleTest, sharedQueriesByHWAddrOnly) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ // No client ID.
+ HWAddrPtr hwaddr = generateHWAddr();
+ // Same hardware address: same client.
+ dis->setHWAddr(hwaddr);
+ req->setHWAddr(hwaddr);
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it with the discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // 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.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ OptionPtr client_id = generateClientId();
+ // Same client ID: same client.
+ dis->addOption(client_id);
+ req->addOption(client_id);
+ HWAddrPtr hwaddr = generateHWAddr();
+ // Same hardware address: same client.
+ dis->setHWAddr(hwaddr);
+ req->setHWAddr(hwaddr);
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it with the discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // 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.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ OptionPtr client_id = generateClientId();
+ OptionPtr client_id2 = generateClientId(111);
+ HWAddrPtr hwaddr = generateHWAddr();
+ HWAddrPtr hwaddr1 = generateHWAddr(55);
+ // Different client ID and hardware address: different client.
+ dis->addOption(client_id);
+ req->addOption(client_id2);
+ dis->setHWAddr(hwaddr);
+ req->setHWAddr(hwaddr1);
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it with the discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // 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 nor hardware address.
+TEST_F(ClientHandleTest, noClientIdHWAddr) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ // No client id nor hardware address: nothing to recognize the client.
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it with the discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // 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) {
+ Pkt4Ptr 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 (id only).
+TEST_F(ClientHandleTest, doubleTryLockById) {
+ // Get a query.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ dis->addOption(generateClientId());
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Try to lock a second time.
+ EXPECT_THROW(client_handler.tryLock(dis), Unexpected);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Verifies that double tryLock call fails (hw only).
+TEST_F(ClientHandleTest, doubleTryLockByHWAddr) {
+ // Get a query without a client ID.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setHWAddr(generateHWAddr());
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Try to lock a second time.
+ EXPECT_THROW(client_handler.tryLock(dis), 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, by client id option version.
+TEST_F(ClientHandleTest, serializeTwoQueriesById) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ OptionPtr client_id = generateClientId();
+ // Same client ID: same client.
+ dis->addOption(client_id);
+ req->addOption(client_id);
+ dis->setHWAddr(generateHWAddr());
+ req->setHWAddr(generateHWAddr(55));
+
+ // 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 discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis, 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, by hardware address version.
+TEST_F(ClientHandleTest, serializeTwoQueriesByHWAddr) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ dis->addOption(generateClientId());
+ req->addOption(generateClientId(111));
+ HWAddrPtr hwaddr = generateHWAddr();
+ // Same hardware address: same client.
+ dis->setHWAddr(hwaddr);
+ req->setHWAddr(hwaddr);
+
+ // 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 discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis, 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. By client id option version.
+TEST_F(ClientHandleTest, serializeNoContById) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ OptionPtr client_id = generateClientId();
+ // Same client ID: same client.
+ dis->addOption(client_id);
+ req->addOption(client_id);
+ dis->setHWAddr(generateHWAddr());
+ req->setHWAddr(generateHWAddr(55));
+
+ // 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 discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // 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 two queries for the same client and multi-threading.
+// Continuations are required for serialization. By hardware address version.
+TEST_F(ClientHandleTest, serializeNoContByHWAddr) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ dis->addOption(generateClientId());
+ req->addOption(generateClientId(111));
+ HWAddrPtr hwaddr = generateHWAddr();
+ // Same hardware address: same client.
+ dis->setHWAddr(hwaddr);
+ req->setHWAddr(hwaddr);
+
+ // 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 discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // 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.
+// By client id option version.
+TEST_F(ClientHandleTest, serializeThreeQueriesById) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ Pkt4Ptr rel(new Pkt4(DHCPRELEASE, 3456));
+ OptionPtr client_id = generateClientId();
+ // Same client ID: same client.
+ dis->addOption(client_id);
+ req->addOption(client_id);
+ rel->addOption(client_id);
+ dis->setHWAddr(generateHWAddr());
+ req->setHWAddr(generateHWAddr(55));
+ rel->setHWAddr(generateHWAddr(66));
+
+ // 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 discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis, 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 release.
+ EXPECT_NO_THROW(duplicate = !client_handler3.tryLock(rel, 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_);
+}
+
+// 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.
+// By hardware address version.
+TEST_F(ClientHandleTest, serializeThreeQueriesHWAddr) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ Pkt4Ptr rel(new Pkt4(DHCPRELEASE, 3456));
+ dis->addOption(generateClientId());
+ req->addOption(generateClientId(111));
+ rel->addOption(generateClientId(99));
+ HWAddrPtr hwaddr = generateHWAddr();
+ // Same hardware address: same client.
+ dis->setHWAddr(hwaddr);
+ req->setHWAddr(hwaddr);
+ rel->setHWAddr(hwaddr);
+
+ // 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 discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis, 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 release.
+ EXPECT_NO_THROW(duplicate = !client_handler3.tryLock(rel, 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_);
+}
+
+// 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.
+// Mixed version (hardware address then client id option duplicates).
+// Note the system is transitive because further races are detected
+// when serialized packet processing is performed.
+TEST_F(ClientHandleTest, serializeThreeQueriesMixed) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ Pkt4Ptr rel(new Pkt4(DHCPRELEASE, 3456));
+ HWAddrPtr hwaddr = generateHWAddr();
+ // Same hardware address: same client for discover and request.
+ dis->setHWAddr(hwaddr);
+ req->setHWAddr(hwaddr);
+ rel->setHWAddr(generateHWAddr(55));
+ OptionPtr client_id = generateClientId();
+ // Same client ID: same client for discover and release.
+ dis->addOption(client_id);
+ req->addOption(generateClientId(111));
+ rel->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 discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis, 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 release.
+ EXPECT_NO_THROW(duplicate = !client_handler3.tryLock(rel, 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/dhcp4/tests/config_backend_unittest.cc b/src/bin/dhcp4/tests/config_backend_unittest.cc
new file mode 100644
index 0000000..ce1b78d
--- /dev/null
+++ b/src/bin/dhcp4/tests/config_backend_unittest.cc
@@ -0,0 +1,506 @@
+// Copyright (C) 2019-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 <arpa/inet.h>
+#include <gtest/gtest.h>
+
+#include <database/backend_selector.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp4/dhcp4_srv.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/json_config_parser.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_subnets4.h>
+#include <dhcpsrv/testutils/generic_backend_unittest.h>
+#include <dhcpsrv/testutils/test_config_backend_dhcp4.h>
+
+#include "dhcp4_test_utils.h"
+#include "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 Dhcp4CBTest : 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 TestConfigBackendDHCPv4(params));
+
+ params[std::string("host")] = std::string("db2");
+ db2_.reset(new TestConfigBackendDHCPv4(params));
+
+ ConfigBackendDHCPv4Mgr::instance().registerBackendFactory("memfile",
+ [this](const DatabaseConnection::ParameterMap& params)
+ -> ConfigBackendDHCPv4Ptr {
+ 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 (TestConfigBackendDHCPv4Ptr(new TestConfigBackendDHCPv4(params)));
+ });
+ }
+
+ /// @brief Clean up after each test
+ virtual void TearDown() {
+ // Unregister the factory to be tidy.
+ ConfigBackendDHCPv4Mgr::instance().unregisterBackendFactory("memfile");
+ }
+
+public:
+
+ /// Constructor
+ Dhcp4CBTest()
+ : 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 ControlledDhcpv4Srv(0));
+
+ // Create fresh context.
+ resetConfiguration();
+ }
+
+ /// Destructor
+ virtual ~Dhcp4CBTest() {
+ resetConfiguration();
+ };
+
+ /// @brief Reset configuration singletons.
+ void resetConfiguration() {
+ CfgMgr::instance().clear();
+ ConfigBackendDHCPv4Mgr::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 = parseDHCP4(config, true);
+ } catch(const std::exception& ex) {
+ ADD_FAILURE() << "parseDHCP4 failed: " << ex.what();
+ }
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+
+ int rcode;
+ ConstElementPtr comment = parseAnswer(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<Dhcpv4Srv> srv_; ///< DHCP4 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
+
+ TestConfigBackendDHCPv4Ptr db1_; ///< First configuration backend instance
+ TestConfigBackendDHCPv4Ptr db2_; ///< Second configuration backend instance
+};
+
+// This test verifies that externally configured globals are
+// merged correctly into staging configuration.
+TEST_F(Dhcp4CBTest, mergeGlobals) {
+ string base_config =
+ "{ \n"
+ " \"interfaces-config\": { \n"
+ " \"interfaces\": [\"*\" ] \n"
+ " }, \n"
+ " \"echo-client-id\": false, \n"
+ " \"decline-probation-period\": 7000, \n"
+ " \"valid-lifetime\": 1000, \n"
+ " \"rebind-timer\": 800, \n"
+ " \"server-hostname\": \"overwrite.me.com\", \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_hostname(new StampedValue("server-hostname", "isc.example.org"));
+ StampedValuePtr decline_period(new StampedValue("decline-probation-period", Element::create(86400)));
+ StampedValuePtr calc_tee_times(new StampedValue("calculate-tee-times", Element::create(bool(false))));
+ StampedValuePtr t2_percent(new StampedValue("t2-percent", Element::create(0.75)));
+ 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_->createUpdateGlobalParameter4(ServerSelector::ALL(), server_hostname);
+ db2_->createUpdateGlobalParameter4(ServerSelector::ALL(), decline_period);
+ db2_->createUpdateGlobalParameter4(ServerSelector::ALL(), calc_tee_times);
+ db2_->createUpdateGlobalParameter4(ServerSelector::ALL(), t2_percent);
+ db2_->createUpdateGlobalParameter4(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();
+
+ // echo-client-id is set explicitly in the original config, meanwhile
+ // the backend config does not set it, so the explicit value wins.
+ EXPECT_FALSE(staging_cfg->getEchoClientId());
+
+ // 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_hostname));
+ ASSERT_NO_FATAL_FAILURE(checkConfiguredGlobal(staging_cfg, calc_tee_times));
+ ASSERT_NO_FATAL_FAILURE(checkConfiguredGlobal(staging_cfg, t2_percent));
+ ASSERT_NO_FATAL_FAILURE(checkConfiguredGlobal(staging_cfg, renew_timer));
+}
+
+// This test verifies that externally configured option definitions
+// merged correctly into staging configuration.
+TEST_F(Dhcp4CBTest, mergeOptionDefs) {
+ string base_config =
+ "{ \n"
+ " \"option-def\": [ { \n"
+ " \"name\": \"one\", \n"
+ " \"code\": 1, \n"
+ " \"type\": \"ipv4-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_->createUpdateOptionDef4(ServerSelector::ALL(), def);
+
+ // Create option three and add it to first backend.
+ def.reset(new OptionDefinition("three", 3, "isc", "string"));
+ db1_->createUpdateOptionDef4(ServerSelector::ALL(), def);
+
+ // Create option four and add it to second backend.
+ def.reset(new OptionDefinition("four", 4, "isc", "string"));
+ db2_->createUpdateOptionDef4(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(Dhcp4CBTest, mergeOptions) {
+ string base_config =
+ "{ \n"
+ " \"option-data\": [ { \n"
+ " \"name\": \"dhcp-message\", \n"
+ " \"data\": \"0A0B0C0D\", \n"
+ " \"csv-format\": false \n"
+ " },{ \n"
+ " \"name\": \"host-name\", \n"
+ " \"data\": \"old.example.com\", \n"
+ " \"csv-format\": true \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);
+
+ OptionDescriptorPtr opt;
+
+ // Add host-name to the first backend.
+ opt.reset(new OptionDescriptor(
+ createOption<OptionString>(Option::V4, DHO_HOST_NAME,
+ true, false, "new.example.com")));
+ opt->space_name_ = DHCP4_OPTION_SPACE;
+ db1_->createUpdateOption4(ServerSelector::ALL(), opt);
+
+ // Add boot-file-name to the first backend.
+ opt.reset(new OptionDescriptor(
+ createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+ true, false, "my-boot-file")));
+ opt->space_name_ = DHCP4_OPTION_SPACE;
+ db1_->createUpdateOption4(ServerSelector::ALL(), opt);
+
+ // Add boot-file-name to the second backend.
+ opt.reset(new OptionDescriptor(
+ createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+ true, false, "your-boot-file")));
+ opt->space_name_ = DHCP4_OPTION_SPACE;
+ db2_->createUpdateOption4(ServerSelector::ALL(), opt);
+
+ // 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();
+
+ // Option definition from JSON should be there.
+ CfgOptionPtr options = staging_cfg->getCfgOption();
+
+ // dhcp-message should come from the original config.
+ OptionDescriptor found_opt =
+ options->get(DHCP4_OPTION_SPACE, DHO_DHCP_MESSAGE);
+ ASSERT_TRUE(found_opt.option_);
+ EXPECT_EQ("0x0A0B0C0D", found_opt.option_->toHexString());
+
+ // host-name should come from the first back end,
+ // (overwriting the original).
+ found_opt = options->get(DHCP4_OPTION_SPACE, DHO_HOST_NAME);
+ ASSERT_TRUE(found_opt.option_);
+ EXPECT_EQ("new.example.com", found_opt.option_->toString());
+
+ // booth-file-name should come from the first back end.
+ found_opt = options->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(found_opt.option_);
+ EXPECT_EQ("my-boot-file", found_opt.option_->toString());
+}
+
+// This test verifies that externally configured shared-networks are
+// merged correctly into staging configuration.
+TEST_F(Dhcp4CBTest, 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
+ SharedNetwork4Ptr network1(new SharedNetwork4("one"));
+ SharedNetwork4Ptr network3(new SharedNetwork4("three"));
+
+ // Add network1 to db1 and network3 to db2
+ db1_->createUpdateSharedNetwork4(ServerSelector::ALL(), network1);
+ db2_->createUpdateSharedNetwork4(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();
+
+ CfgSharedNetworks4Ptr networks = staging_cfg->getCfgSharedNetworks4();
+ SharedNetwork4Ptr 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(Dhcp4CBTest, 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"
+ " \"subnet4\": [ \n"
+ " { \n"
+ " \"id\": 2,\n"
+ " \"subnet\": \"192.0.3.0/24\" \n"
+ " } ]\n"
+ "} \n";
+
+ extractConfig(base_config);
+
+ // Make a few subnets
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3, SubnetID(1)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.4.0"), 26, 1, 2, 3, SubnetID(3)));
+
+ // Add subnet1 to db1 and subnet3 to db2
+ db1_->createUpdateSubnet4(ServerSelector::ALL(), subnet1);
+ db2_->createUpdateSubnet4(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();
+
+ CfgSubnets4Ptr subnets = staging_cfg->getCfgSubnets4();
+ ConstSubnet4Ptr 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/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc
new file mode 100644
index 0000000..01b625f
--- /dev/null
+++ b/src/bin/dhcp4/tests/config_parser_unittest.cc
@@ -0,0 +1,7577 @@
+// Copyright (C) 2012-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 <arpa/inet.h>
+#include <gtest/gtest.h>
+
+#include <cc/command_interpreter.h>
+#include <dhcp4/dhcp4_srv.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/json_config_parser.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/classify.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_expiration.h>
+#include <dhcpsrv/cfg_hosts.h>
+#include <dhcpsrv/cfg_subnets4.h>
+#include <dhcpsrv/parsers/simple_parser4.h>
+#include <dhcpsrv/testutils/config_result_check.h>
+#include <dhcpsrv/testutils/test_config_backend_dhcp4.h>
+#include <process/config_ctl_info.h>
+#include <hooks/hooks_manager.h>
+#include <stats/stats_mgr.h>
+#include <testutils/log_utils.h>
+#include <testutils/gtest_utils.h>
+#include <util/chrono_time_utils.h>
+#include <util/doubles.h>
+
+#include "marker_file.h"
+#include "test_libraries.h"
+#include "test_data_files_config.h"
+#include "dhcp4_test_utils.h"
+#include "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;
+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,"
+ " \"rebind-timer\": 2000,"
+ " \"renew-timer\": 1000,"
+ " \"subnet4\": [ {"
+ " \"pools\": [ "
+ " { \"pool\": \"192.0.2.0/28\" }"
+ " ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ]"
+ "}",
+
+ // Configuration 1: one pool with empty user context
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [\"*\" ]"
+ " },"
+ " \"valid-lifetime\": 4000,"
+ " \"rebind-timer\": 2000,"
+ " \"renew-timer\": 1000,"
+ " \"subnet4\": [ {"
+ " \"pools\": [ "
+ " { \"pool\": \"192.0.2.0/28\","
+ " \"user-context\": {"
+ " }"
+ " }"
+ " ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ]"
+ "}",
+
+ // Configuration 2: one pool with user context containing lw4over6 parameters
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [\"*\" ]"
+ " },"
+ " \"valid-lifetime\": 4000,"
+ " \"rebind-timer\": 2000,"
+ " \"renew-timer\": 1000,"
+ " \"subnet4\": [ {"
+ " \"pools\": [ "
+ " { \"pool\": \"192.0.2.0/28\","
+ " \"user-context\": {"
+ " \"integer-param\": 42,"
+ " \"string-param\": \"Sagittarius\","
+ " \"bool-param\": true"
+ " }"
+ " }"
+ " ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ]"
+ "}",
+
+ // Configuration 3: one min-max pool with user context containing lw4over6 parameters
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [\"*\" ]"
+ " },"
+ " \"valid-lifetime\": 4000,"
+ " \"rebind-timer\": 2000,"
+ " \"renew-timer\": 1000,"
+ " \"subnet4\": [ {"
+ " \"pools\": [ "
+ " { \"pool\": \"192.0.2.0 - 192.0.2.15\","
+ " \"user-context\": {"
+ " \"integer-param\": 42,"
+ " \"string-param\": \"Sagittarius\","
+ " \"bool-param\": true"
+ " }"
+ " }"
+ " ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ]"
+ "}",
+
+ // Configuration 4: two host databases
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [\"*\" ]"
+ " },"
+ " \"valid-lifetime\": 4000,"
+ " \"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 5 for comments
+ "{"
+ " \"comment\": \"A DHCPv4 server\","
+ " \"interfaces-config\": {"
+ " \"comment\": \"Use wildcard\","
+ " \"interfaces\": [ \"*\" ] },"
+ " \"option-def\": [ {"
+ " \"comment\": \"An option definition\","
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"isc\""
+ " } ],"
+ " \"option-data\": [ {"
+ " \"comment\": \"Set option value\","
+ " \"name\": \"dhcp-message\","
+ " \"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/kea4-ctrl-socket\","
+ " \"user-context\": { \"comment\": \"Indirect comment\" }"
+ " },"
+ " \"shared-networks\": [ {"
+ " \"comment\": \"A shared network\","
+ " \"name\": \"foo\","
+ " \"subnet4\": ["
+ " { "
+ " \"comment\": \"A subnet\","
+ " \"subnet\": \"192.0.1.0/24\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"comment\": \"A pool\","
+ " \"pool\": \"192.0.1.1-192.0.1.10\""
+ " }"
+ " ],"
+ " \"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-name\","
+ " \"data\": \"example.com\""
+ " } ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " } ],"
+ " \"dhcp-ddns\": {"
+ " \"comment\": \"No dynamic DNS\","
+ " \"enable-updates\": false"
+ " }"
+ "}",
+
+ // Configuration 6: config databases
+ "{ \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"
+};
+
+class Dhcp4ParserTest : 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:
+ Dhcp4ParserTest()
+ : rcode_(-1) {
+ // 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 ControlledDhcpv4Srv(0));
+ // Create fresh context.
+ resetConfiguration();
+ }
+
+public:
+
+ // Checks if the 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_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(expected_code, rcode_) << "error text:" << comment_->stringValue();
+ }
+
+ // 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_ = parseAnswer(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;
+ try {
+ json = parseDHCP4(config, true);
+ } catch(const std::exception& ex) {
+ ADD_FAILURE() << "parseDHCP4 failed: " << ex.what();
+ }
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+
+ int rcode;
+ ConstElementPtr comment = parseAnswer(rcode, status);
+ EXPECT_EQ(expected_code, rcode);
+
+ 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()) {
+ EXPECT_EQ(exp_error, text);
+ }
+ }
+ }
+
+ ~Dhcp4ParserTest() {
+ resetConfiguration();
+
+ // ... and delete the hooks library marker files if present
+ static_cast<void>(remove(LOAD_MARKER_FILE));
+ static_cast<void>(remove(UNLOAD_MARKER_FILE));
+ };
+
+ /// @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.
+ /// @return configuration string containing custom values of parameters
+ /// describing an option.
+ 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"] = DHCP4_OPTION_SPACE;
+ params["code"] = "56";
+ params["data"] = "ABCDEF0105";
+ params["csv-format"] = "false";
+ } else if (parameter == "space") {
+ params["name"] = "dhcp-message";
+ params["space"] = param_value;
+ params["code"] = "56";
+ params["data"] = "ABCDEF0105";
+ params["csv-format"] = "false";
+ } else if (parameter == "code") {
+ params["name"] = "dhcp-message";
+ params["space"] = DHCP4_OPTION_SPACE;
+ params["code"] = param_value;
+ params["data"] = "ABCDEF0105";
+ params["csv-format"] = "false";
+ } else if (parameter == "data") {
+ params["name"] = "dhcp-message";
+ params["space"] = DHCP4_OPTION_SPACE;
+ params["code"] = "56";
+ params["data"] = param_value;
+ params["csv-format"] = "false";
+ } else if (parameter == "csv-format") {
+ params["name"] = "dhcp-message";
+ params["space"] = DHCP4_OPTION_SPACE;
+ params["code"] = "56";
+ 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() << "," <<
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"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) {
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(subnet_address);
+ if (!subnet) {
+ /// @todo replace toText() with the use of operator <<.
+ ADD_FAILURE() << "A subnet for the specified address "
+ << subnet_address.toText()
+ << "does not exist in Config Manager";
+ }
+ OptionContainerPtr options =
+ subnet->getCfgOption()->getAll(DHCP4_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));
+ }
+
+ return (*range.first);
+ }
+
+ /// @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 = parseDHCP4(config);
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 1);
+ EXPECT_TRUE(errorContainsPosition(x, "<string>"));
+ }
+
+ /// @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 = parseDHCP4(config);
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 1);
+ EXPECT_TRUE(errorContainsPosition(x, "<string>"));
+ }
+
+ /// @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) {
+ std::string config = createConfigWithOption(params);
+ ASSERT_TRUE(executeConfiguration(config, "parse option configuration"));
+ // The subnet should now hold one option with the specified option code.
+ OptionDescriptor desc =
+ getOptionFromSubnet(IOAddress("192.0.2.24"), option_code);
+ ASSERT_TRUE(desc.option_);
+ testOption(desc, option_code, expected_data, expected_data_len);
+ }
+
+ /// @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) {
+ CfgMgr::instance().clear();
+ ConstElementPtr json;
+ ConstElementPtr status;
+ try {
+ json = parseJSON(config);
+ status = configureDhcp4Server(*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_ = parseAnswer(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 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 = "{ " + genIfaceConfig() + "," +
+ "\"hooks-libraries\": [ ], "
+ "\"valid-lifetime\": 4000, "
+ "\"subnet4\": [ ], "
+ "\"dhcp-ddns\": { \"enable-updates\" : false }, "
+ "\"option-def\": [ ], "
+ "\"option-data\": [ ] }";
+ CfgMgr::instance().rollback();
+ static_cast<void>(executeConfiguration(config,
+ "reset configuration database"));
+ CfgMgr::instance().clear();
+ }
+
+ /// @brief Retrieve an option associated with a host.
+ ///
+ /// The option is retrieved from the "dhcp4" 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, DHCP4_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.getCfgOption4();
+ 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 Checks if specified subnet is part of the collection
+ ///
+ /// @tparam CollectionType type of subnet4 collections i.e.
+ /// either Subnet4SimpleCollection or Subnet4Collection
+ /// @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 valid expected valid-lifetime value
+ /// @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>
+ Subnet4Ptr
+ checkSubnet(const CollectionType& col, std::string subnet,
+ uint32_t t1, uint32_t t2, uint32_t valid,
+ 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 (Subnet4Ptr());
+ }
+ Subnet4Ptr s = *subnet_it;
+
+ EXPECT_EQ(t1, s->getT1());
+ EXPECT_EQ(t2, s->getT2());
+ EXPECT_EQ(valid, s->getValid());
+ EXPECT_EQ(min_valid ? min_valid : valid, s->getValid().getMin());
+ EXPECT_EQ(max_valid ? max_valid : valid, s->getValid().getMax());
+
+ return (s);
+ }
+
+ /// @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 pool [out] Pool pointer will be stored here (if found)
+ void getPool(const std::string& config, size_t subnet_index,
+ size_t pool_index, PoolPtr& pool) {
+ ConstElementPtr status;
+ ConstElementPtr json;
+
+ EXPECT_NO_THROW(json = parseDHCP4(config, true));
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ ConstCfgSubnets4Ptr subnets4 = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+ ASSERT_TRUE(subnets4);
+
+ const Subnet4Collection* subnets = subnets4->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(Lease::TYPE_V4);
+ 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);
+ }
+
+ boost::scoped_ptr<Dhcpv4Srv> srv_; ///< DHCP4 server under test
+ int rcode_; ///< Return code from element parsing
+ ConstElementPtr comment_; ///< Reason for parse fail
+ 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(Dhcp4ParserTest, bogusCommand) {
+
+ ConstElementPtr x;
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_,
+ parseJSON("{\"bogus\": 5}")));
+
+ // returned value must be 1 (configuration parse error)
+ checkResult(x, 1);
+
+ // it should be refused by syntax too
+ EXPECT_THROW(parseDHCP4("{\"bogus\": 5}"), Dhcp4ParseError);
+}
+
+/// The goal of this test is to verify empty interface-config is accepted.
+TEST_F(Dhcp4ParserTest, emptyInterfaceConfig) {
+
+ ConstElementPtr json;
+ EXPECT_NO_THROW(json = parseDHCP4("{ \"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000 }"));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+ // returned value should be 0 (success)
+ checkResult(status, 0);
+}
+
+/// The goal of this test is to verify if wrongly defined subnet will
+/// be rejected. Properly defined subnet must include at least one
+/// pool definition.
+TEST_F(Dhcp4ParserTest, emptySubnet) {
+
+ std::string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ ], "
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ EXPECT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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(Dhcp4ParserTest, outBoundValidLifetime) {
+
+ string too_small = "{ " + genIfaceConfig() + "," +
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 1000, \"min-valid-lifetime\": 2000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(too_small));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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() + "," +
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 2000, \"max-valid-lifetime\": 1000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP4(too_large));
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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() + "," +
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 1000, \"min-valid-lifetime\": 2000, "
+ "\"max-valid-lifetime\": 4000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP4(before));
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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() + "," +
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 5000, \"min-valid-lifetime\": 1000, "
+ "\"max-valid-lifetime\": 4000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP4(after));
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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() + "," +
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 1500, \"min-valid-lifetime\": 2000, "
+ "\"max-valid-lifetime\": 1000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP4(crossed));
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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(Dhcp4ParserTest, outBoundGlobalValidLifetime) {
+
+ string too_small = "{ " + genIfaceConfig() + "," +
+ "\"valid-lifetime\": 1000, \"min-valid-lifetime\": 2000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(too_small));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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 = parseDHCP4(too_large));
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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 = parseDHCP4(before));
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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 = parseDHCP4(after));
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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 = parseDHCP4(crossed));
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ expected =
+ "the value of min-valid-lifetime (2000) is not "
+ "less than max-valid-lifetime (1000)";
+ checkResult(status, 1, expected);
+}
+
+/// Check that the renew-timer doesn't have to be specified, in which case
+/// it is marked unspecified in the Subnet.
+TEST_F(Dhcp4ParserTest, unspecifiedRenewTimer) {
+
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+ // returned value should be 0 (success)
+ checkResult(status, 0);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+
+ EXPECT_TRUE(subnet->getT1().unspecified());
+ EXPECT_FALSE(subnet->getT2().unspecified());
+ EXPECT_EQ(2000, subnet->getT2());
+ EXPECT_EQ(4000, subnet->getValid());
+
+ // Check that subnet-id is 1
+ EXPECT_EQ(1, subnet->getID());
+}
+
+/// Check that the rebind-timer doesn't have to be specified, in which case
+/// it is marked unspecified in the Subnet.
+TEST_F(Dhcp4ParserTest, unspecifiedRebindTimer) {
+
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+ // returned value should be 0 (success)
+ checkResult(status, 0);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getT1().unspecified());
+ EXPECT_EQ(1000, subnet->getT1());
+ EXPECT_TRUE(subnet->getT2().unspecified());
+ EXPECT_EQ(4000, subnet->getValid());
+
+ // Check that subnet-id is 1
+ EXPECT_EQ(1, subnet->getID());
+}
+
+/// The goal of this test is to verify if defined subnet uses global
+/// parameter timer definitions.
+TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
+
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000,"
+ "\"min-valid-lifetime\": 3000,"
+ "\"max-valid-lifetime\": 5000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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.
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ(1000, subnet->getT1());
+ EXPECT_EQ(2000, subnet->getT2());
+ EXPECT_EQ(4000, subnet->getValid());
+ EXPECT_EQ(3000, subnet->getValid().getMin());
+ EXPECT_EQ(5000, subnet->getValid().getMax());
+
+ // Check that subnet-id is 1
+ EXPECT_EQ(1, subnet->getID());
+}
+
+// Goal of this test is to verify that multiple subnets get unique
+// subnet-ids. Also, test checks that it's possible to do reconfiguration
+// multiple times.
+TEST_F(Dhcp4ParserTest, multipleSubnets) {
+ ConstElementPtr x;
+ // Collection of four subnets for which subnet ids should be
+ // autogenerated - ids are unspecified or set to 0.
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.3.101 - 192.0.3.150\" } ],"
+ " \"subnet\": \"192.0.3.0/24\", "
+ " \"id\": 0 "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
+ " \"subnet\": \"192.0.4.0/24\" "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.5.101 - 192.0.5.150\" } ],"
+ " \"subnet\": \"192.0.5.0/24\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ int cnt = 0; // Number of reconfigurations
+
+ do {
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 0);
+
+ CfgMgr::instance().commit();
+
+ const Subnet4Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->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(Dhcp4ParserTest, multipleSubnetsExplicitIDs) {
+ ConstElementPtr x;
+ // Four subnets with arbitrary subnet ids.
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"id\": 1024 "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.3.101 - 192.0.3.150\" } ],"
+ " \"subnet\": \"192.0.3.0/24\", "
+ " \"id\": 100 "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
+ " \"subnet\": \"192.0.4.0/24\", "
+ " \"id\": 1 "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.5.101 - 192.0.5.150\" } ],"
+ " \"subnet\": \"192.0.5.0/24\", "
+ " \"id\": 34 "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ int cnt = 0; // Number of reconfigurations
+ do {
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 0);
+
+ CfgMgr::instance().commit();
+
+ const Subnet4Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
+
+ // Verify 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(Dhcp4ParserTest, multipleSubnetsOverlappingIDs) {
+ ConstElementPtr x;
+ // Four subnets, two of them having the same id.
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"id\": 1024 "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.3.101 - 192.0.3.150\" } ],"
+ " \"subnet\": \"192.0.3.0/24\", "
+ " \"id\": 100 "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
+ " \"subnet\": \"192.0.4.0/24\", "
+ " \"id\": 1024 "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.5.101 - 192.0.5.150\" } ],"
+ " \"subnet\": \"192.0.5.0/24\", "
+ " \"id\": 34 "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*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(Dhcp4ParserTest, reconfigureRemoveSubnet) {
+ ConstElementPtr x;
+
+ // All four subnets
+ string config4 = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"id\": 1 "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.3.101 - 192.0.3.150\" } ],"
+ " \"subnet\": \"192.0.3.0/24\", "
+ " \"id\": 2 "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
+ " \"subnet\": \"192.0.4.0/24\", "
+ " \"id\": 3 "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.5.101 - 192.0.5.150\" } ],"
+ " \"subnet\": \"192.0.5.0/24\", "
+ " \"id\": 4 "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Three subnets (the last one removed)
+ string config_first3 = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"id\": 1 "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.3.101 - 192.0.3.150\" } ],"
+ " \"subnet\": \"192.0.3.0/24\", "
+ " \"id\": 2 "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
+ " \"subnet\": \"192.0.4.0/24\", "
+ " \"id\": 3 "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Second subnet removed
+ string config_second_removed = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"id\": 1 "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
+ " \"subnet\": \"192.0.4.0/24\", "
+ " \"id\": 3 "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.5.101 - 192.0.5.150\" } ],"
+ " \"subnet\": \"192.0.5.0/24\", "
+ " \"id\": 4 "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // CASE 1: Configure 4 subnets, then reconfigure and remove the
+ // last one.
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config4));
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 0);
+
+ const Subnet4Collection* subnets =
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
+
+ CfgMgr::instance().clear();
+
+ // Do the reconfiguration (the last subnet is removed)
+ ASSERT_NO_THROW(json = parseDHCP4(config_first3));
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 0);
+
+ subnets = CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(3, subnets->size()); // We expect 3 subnets now (4th is removed)
+
+ // 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());
+
+ CfgMgr::instance().clear();
+
+ /// CASE 2: Configure 4 subnets, then reconfigure and remove one
+ /// from in between (not first, not last)
+ ASSERT_NO_THROW(json = parseDHCP4(config4));
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 0);
+
+ CfgMgr::instance().clear();
+
+ // Do reconfiguration
+ ASSERT_NO_THROW(json = parseDHCP4(config_second_removed));
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 0);
+
+ subnets = CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(3, subnets->size()); // We expect 4 subnets
+
+ auto subnet_it = subnets->begin();
+ EXPECT_EQ(1, (*subnet_it)->getID());
+ // The second subnet (with subnet-id = 2) is no longer there
+ EXPECT_EQ(3, (*++subnet_it)->getID());
+ EXPECT_EQ(4, (*++subnet_it)->getID());
+}
+
+/// @todo: implement subnet removal test as part of #3281.
+
+// Checks if the next-server and other fixed BOOTP fields defined as
+// global parameter are taken into consideration.
+TEST_F(Dhcp4ParserTest, nextServerGlobal) {
+
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"next-server\": \"1.2.3.4\", "
+ "\"server-hostname\": \"foo\", "
+ "\"boot-file-name\": \"bar\", "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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.
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+ // Reset the fetch global function to staging (vs current) config.
+ subnet->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getStagingCfg()->getConfiguredGlobals());
+ });
+ EXPECT_EQ("1.2.3.4", subnet->getSiaddr().get().toText());
+ EXPECT_EQ("foo", subnet->getSname().get());
+ EXPECT_EQ("bar", subnet->getFilename().get());
+}
+
+// Checks if the next-server and other fixed BOOTP fields defined as
+// subnet parameter are taken into consideration.
+TEST_F(Dhcp4ParserTest, nextServerSubnet) {
+
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"next-server\": \"1.2.3.4\", "
+ " \"server-hostname\": \"foo\", "
+ " \"boot-file-name\": \"bar\", "
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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.
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ("1.2.3.4", subnet->getSiaddr().get().toText());
+ EXPECT_EQ("foo", subnet->getSname().get());
+ EXPECT_EQ("bar", subnet->getFilename().get());
+}
+
+// Test checks several negative scenarios for next-server configuration: bogus
+// address, IPv6 address and empty string.
+TEST_F(Dhcp4ParserTest, nextServerNegative) {
+ IfaceMgrTestConfig test_config(true);
+
+ // Config with junk instead of next-server address
+ string config_bogus1 = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"next-server\": \"a.b.c.d\", "
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Config with IPv6 next server address
+ string config_bogus2 = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"next-server\": \"2001:db8::1\", "
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Config with empty next server address
+ string config_bogus3 = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"next-server\": \"\", "
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Config with too large server-hostname
+ string bigsname(Pkt4::MAX_SNAME_LEN + 1, ' ');
+ string config_bogus4 = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"server-hostname\": \"" + bigsname + "\", " +
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Config with too large boot-file-hostname
+ string bigfilename(Pkt4::MAX_FILE_LEN + 1, ' ');
+ string config_bogus5 = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"boot-file-name\": \"" + bigfilename + "\", " +
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json1;
+ ASSERT_NO_THROW(json1 = parseDHCP4(config_bogus1));
+ ConstElementPtr json2;
+ ASSERT_NO_THROW(json2 = parseDHCP4(config_bogus2));
+ ConstElementPtr json3;
+ ASSERT_NO_THROW(json3 = parseDHCP4(config_bogus3));
+ ConstElementPtr json4;
+ ASSERT_NO_THROW(json4 = parseDHCP4(config_bogus4));
+ ConstElementPtr json5;
+ ASSERT_NO_THROW(json5 = parseDHCP4(config_bogus5));
+
+ // check if returned status is always a failure
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json1));
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json2));
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json3));
+ checkResult(status, 0);
+ EXPECT_FALSE(errorContainsPosition(status, "<string>"));
+
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json4));
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json5));
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+}
+
+// Checks if the next-server defined as global value is overridden by subnet
+// specific value.
+TEST_F(Dhcp4ParserTest, nextServerOverride) {
+
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"next-server\": \"192.0.0.1\", "
+ "\"server-hostname\": \"nohost\","
+ "\"boot-file-name\": \"nofile\","
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"next-server\": \"1.2.3.4\", "
+ " \"server-hostname\": \"some-name.example.org\","
+ " \"boot-file-name\": \"bootfile.efi\","
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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.
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ("1.2.3.4", subnet->getSiaddr().get().toText());
+ EXPECT_EQ("some-name.example.org", subnet->getSname().get());
+ EXPECT_EQ("bootfile.efi", subnet->getFilename().get());
+}
+
+// Check whether it is possible to configure echo-client-id
+TEST_F(Dhcp4ParserTest, echoClientId) {
+
+ string config_false = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"echo-client-id\": false,"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ string config_true = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"echo-client-id\": true,"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json_false;
+ ASSERT_NO_THROW(json_false = parseDHCP4(config_false));
+ extractConfig(config_false);
+ ConstElementPtr json_true;
+ ASSERT_NO_THROW(json_true = parseDHCP4(config_true));
+ extractConfig(config_true);
+
+ // Let's check the default. It should be true
+ ASSERT_TRUE(CfgMgr::instance().getStagingCfg()->getEchoClientId());
+
+ // Now check that "false" configuration is really applied.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json_false));
+ ASSERT_FALSE(CfgMgr::instance().getStagingCfg()->getEchoClientId());
+
+ CfgMgr::instance().clear();
+
+ // Now check that "true" configuration is really applied.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json_true));
+ ASSERT_TRUE(CfgMgr::instance().getStagingCfg()->getEchoClientId());
+
+ // In any case revert back to the default value (true)
+ CfgMgr::instance().getStagingCfg()->setEchoClientId(true);
+}
+
+// This test checks that the global match-client-id parameter is optional
+// and that values under the subnet are used.
+TEST_F(Dhcp4ParserTest, matchClientIdNoGlobal) {
+ std::string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ "
+ "{"
+ " \"match-client-id\": true,"
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ "},"
+ "{"
+ " \"match-client-id\": false,"
+ " \"pools\": [ { \"pool\": \"192.0.3.1 - 192.0.3.100\" } ],"
+ " \"subnet\": \"192.0.3.0/24\""
+ "} ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ checkResult(status, 0);
+
+ CfgSubnets4Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+ Subnet4Ptr subnet1 = cfg->selectSubnet(IOAddress("192.0.2.1"));
+ ASSERT_TRUE(subnet1);
+ // Reset the fetch global function to staging (vs current) config.
+ subnet1->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getStagingCfg()->getConfiguredGlobals());
+ });
+ EXPECT_TRUE(subnet1->getMatchClientId());
+
+ Subnet4Ptr subnet2 = cfg->selectSubnet(IOAddress("192.0.3.1"));
+ ASSERT_TRUE(subnet2);
+ // Reset the fetch global function to staging (vs current) config.
+ subnet2->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getStagingCfg()->getConfiguredGlobals());
+ });
+ EXPECT_FALSE(subnet2->getMatchClientId());
+}
+
+// This test checks that the global match-client-id parameter is used
+// when there is no such parameter under subnet and that the parameter
+// specified for a subnet overrides the global setting.
+TEST_F(Dhcp4ParserTest, matchClientIdGlobal) {
+ std::string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"match-client-id\": true,"
+ "\"subnet4\": [ "
+ "{"
+ " \"match-client-id\": false,"
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ "},"
+ "{"
+ " \"pools\": [ { \"pool\": \"192.0.3.1 - 192.0.3.100\" } ],"
+ " \"subnet\": \"192.0.3.0/24\""
+ "} ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ checkResult(status, 0);
+
+ CfgSubnets4Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+ Subnet4Ptr subnet1 = cfg->selectSubnet(IOAddress("192.0.2.1"));
+ ASSERT_TRUE(subnet1);
+ // Reset the fetch global function to staging (vs current) config.
+ subnet1->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getStagingCfg()->getConfiguredGlobals());
+ });
+ EXPECT_FALSE(subnet1->getMatchClientId());
+
+ Subnet4Ptr subnet2 = cfg->selectSubnet(IOAddress("192.0.3.1"));
+ ASSERT_TRUE(subnet2);
+ // Reset the fetch global function to staging (vs current) config.
+ subnet2->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getStagingCfg()->getConfiguredGlobals());
+ });
+ EXPECT_TRUE(subnet2->getMatchClientId());
+}
+
+// This test checks that the global authoritative parameter is optional
+// and that values under the subnet are used.
+TEST_F(Dhcp4ParserTest, authoritativeNoGlobal) {
+ std::string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ "
+ "{"
+ " \"authoritative\": true,"
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ "},"
+ "{"
+ " \"authoritative\": false,"
+ " \"pools\": [ { \"pool\": \"192.0.3.1 - 192.0.3.100\" } ],"
+ " \"subnet\": \"192.0.3.0/24\""
+ "} ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ checkResult(status, 0);
+
+ CfgSubnets4Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+ Subnet4Ptr subnet1 = cfg->selectSubnet(IOAddress("192.0.2.1"));
+ ASSERT_TRUE(subnet1);
+ // Reset the fetch global function to staging (vs current) config.
+ subnet1->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getStagingCfg()->getConfiguredGlobals());
+ });
+ EXPECT_TRUE(subnet1->getAuthoritative());
+
+ Subnet4Ptr subnet2 = cfg->selectSubnet(IOAddress("192.0.3.1"));
+ ASSERT_TRUE(subnet2);
+ // Reset the fetch global function to staging (vs current) config.
+ subnet2->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getStagingCfg()->getConfiguredGlobals());
+ });
+ EXPECT_FALSE(subnet2->getAuthoritative());
+}
+
+// This test checks that the global authoritative parameter is used
+// when there is no such parameter under subnet and that the parameter
+// specified for a subnet overrides the global setting.
+TEST_F(Dhcp4ParserTest, authoritativeGlobal) {
+ std::string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"authoritative\": true,"
+ "\"subnet4\": [ "
+ "{"
+ " \"authoritative\": false,"
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ "},"
+ "{"
+ " \"pools\": [ { \"pool\": \"192.0.3.1 - 192.0.3.100\" } ],"
+ " \"subnet\": \"192.0.3.0/24\""
+ "} ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ checkResult(status, 0);
+
+ CfgSubnets4Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+ Subnet4Ptr subnet1 = cfg->selectSubnet(IOAddress("192.0.2.1"));
+ ASSERT_TRUE(subnet1);
+ // Reset the fetch global function to staging (vs current) config.
+ subnet1->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getStagingCfg()->getConfiguredGlobals());
+ });
+ EXPECT_FALSE(subnet1->getAuthoritative());
+
+ Subnet4Ptr subnet2 = cfg->selectSubnet(IOAddress("192.0.3.1"));
+ ASSERT_TRUE(subnet2);
+ // Reset the fetch global function to staging (vs current) config.
+ subnet2->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getStagingCfg()->getConfiguredGlobals());
+ });
+ EXPECT_TRUE(subnet2->getAuthoritative());
+}
+
+// This test checks if it is possible to override global values
+// on a per subnet basis.
+TEST_F(Dhcp4ParserTest, subnetLocal) {
+
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"renew-timer\": 1, "
+ " \"rebind-timer\": 2, "
+ " \"valid-lifetime\": 4,"
+ " \"min-valid-lifetime\": 3,"
+ " \"max-valid-lifetime\": 5,"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000,"
+ "\"min-valid-lifetime\": 3000,"
+ "\"max-valid-lifetime\": 5000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+ // returned value should be 0 (configuration success)
+ checkResult(status, 0);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ(1, subnet->getT1());
+ EXPECT_EQ(2, subnet->getT2());
+ EXPECT_EQ(4, subnet->getValid());
+ EXPECT_EQ(3, subnet->getValid().getMin());
+ EXPECT_EQ(5, subnet->getValid().getMax());
+}
+
+// This test checks that multiple pools can be defined and handled properly.
+// The test defines 2 subnets, each with 2 pools.
+TEST_F(Dhcp4ParserTest, multiplePools) {
+
+ // Collection with two subnets, each with 2 pools.
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ "
+ " { \"pool\": \"192.0.2.0/28\" },"
+ " { \"pool\": \"192.0.2.200-192.0.2.255\" }"
+ " ],"
+ " \"subnet\": \"192.0.2.0/24\" "
+ " },"
+ " {"
+ " \"pools\": [ "
+ " { \"pool\": \"192.0.3.0/25\" },"
+ " { \"pool\": \"192.0.3.128/25\" }"
+ " ],"
+ " \"subnet\": \"192.0.3.0/24\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ checkResult(status, 0);
+
+ const Subnet4Collection* subnets =
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->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_V4);
+ ASSERT_EQ(2, pools1.size());
+ EXPECT_EQ("type=V4, 192.0.2.0-192.0.2.15",
+ pools1[0]->toText());
+ EXPECT_EQ("type=V4, 192.0.2.200-192.0.2.255",
+ pools1[1]->toText());
+ // There shouldn't be any TA or PD pools
+ EXPECT_THROW((*subnet)->getPools(Lease::TYPE_TA), BadValue);
+ EXPECT_THROW((*subnet)->getPools(Lease::TYPE_PD), BadValue);
+
+ // Check the second subnet
+ ++subnet;
+ const PoolCollection& pools2 = (*subnet)->getPools(Lease::TYPE_V4);
+ ASSERT_EQ(2, pools2.size());
+ EXPECT_EQ("type=V4, 192.0.3.0-192.0.3.127",
+ pools2[0]->toText());
+ EXPECT_EQ("type=V4, 192.0.3.128-192.0.3.255",
+ pools2[1]->toText());
+ // There shouldn't be any TA or PD pools
+ EXPECT_THROW((*subnet)->getPools(Lease::TYPE_TA), BadValue);
+ EXPECT_THROW((*subnet)->getPools(Lease::TYPE_PD), BadValue);
+}
+
+// Test verifies that a subnet with pool values that do not belong to that
+// pool are rejected.
+TEST_F(Dhcp4ParserTest, poolOutOfSubnet) {
+
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.4.0/28\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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.
+TEST_F(Dhcp4ParserTest, poolPrefixLen) {
+
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.128/28\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+ // returned value must be 0 (configuration accepted)
+ checkResult(status, 0);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ(1000, subnet->getT1());
+ EXPECT_EQ(2000, subnet->getT2());
+ EXPECT_EQ(4000, subnet->getValid());
+}
+
+// Goal of this test is to verify if invalid pool definitions
+// return a location in the error message.
+TEST_F(Dhcp4ParserTest, badPools) {
+
+ // not a prefix
+ string config_bogus1 = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"foo/28\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // not a length
+ string config_bogus2 = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.128/foo\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // invalid prefix length
+ string config_bogus3 = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.128/100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // not a prefix nor a min-max
+ string config_bogus4 = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"foo\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // not an address
+ string config_bogus5 = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"foo - bar\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // min > max
+ string config_bogus6 = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.200 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // out of range prefix length (new check)
+ string config_bogus7 = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.128/1052\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json1;
+ ASSERT_NO_THROW(json1 = parseDHCP4(config_bogus1));
+ ConstElementPtr json2;
+ ASSERT_NO_THROW(json2 = parseDHCP4(config_bogus2));
+ ConstElementPtr json3;
+ ASSERT_NO_THROW(json3 = parseDHCP4(config_bogus3));
+ ConstElementPtr json4;
+ ASSERT_NO_THROW(json4 = parseDHCP4(config_bogus4));
+ ConstElementPtr json5;
+ ASSERT_NO_THROW(json5 = parseDHCP4(config_bogus5));
+ ConstElementPtr json6;
+ ASSERT_NO_THROW(json6 = parseDHCP4(config_bogus6));
+ ConstElementPtr json7;
+ ASSERT_NO_THROW(json7 = parseDHCP4(config_bogus7));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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 = configureDhcp4Server(*srv_, json2));
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json3));
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json4));
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json5));
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json6));
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json7));
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+}
+
+// Goal of this test is to verify no pool definitions is invalid
+// and returns a location in the error message.
+TEST_F(Dhcp4ParserTest, noPools) {
+
+ // Configuration string.
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"user-context\": { } } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ EXPECT_THROW(parseDHCP4(config, true), Dhcp4ParseError);
+}
+
+// Goal of this test is to verify that invalid subnet fails to be parsed.
+TEST_F(Dhcp4ParserTest, 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",
+ "{ \"subnet4\": [ { "
+ " \"subnet\": \"not an address/24\" } ],"
+ "\"valid-lifetime\": 4000 }",
+ "subnet configuration failed: "
+ "Failed to convert string to address 'notanaddress': Invalid argument"
+ },
+ {
+ "IP is Invalid",
+ "{ \"subnet4\": [ { "
+ " \"subnet\": \"256.16.1.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }",
+ "subnet configuration failed: "
+ "Failed to convert string to address '256.16.1.0': Invalid argument"
+ },
+ {
+ "Missing prefix",
+ "{ \"subnet4\": [ { "
+ " \"subnet\": \"192.0.2.0\" } ],"
+ "\"valid-lifetime\": 4000 }",
+ "subnet configuration failed: "
+ "Invalid subnet syntax (prefix/len expected):192.0.2.0 (<string>:1:32)"
+ },
+ {
+ "Prefix not an integer (2 slashes)",
+ "{ \"subnet4\": [ { "
+ " \"subnet\": \"192.0.2.0//24\" } ],"
+ "\"valid-lifetime\": 4000 }",
+ "subnet configuration failed: "
+ "prefix length: '/24' is not an integer (<string>:1:32)"
+ },
+ {
+ "Prefix value is insane",
+ "{ \"subnet4\": [ { "
+ " \"subnet\": \"192.0.2.0/45938\" } ],"
+ "\"valid-lifetime\": 4000 }",
+ "subnet configuration failed: "
+ "Invalid prefix length specified for subnet: 45938 (<string>:1:32)"
+ }
+ };
+
+ // 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 = parseDHCP4((*scenario).config_json_))
+ << "invalid json, broken test";
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, config));
+ checkResult(status, 1);
+ EXPECT_EQ(comment_->stringValue(), (*scenario).exp_error_msg_);
+ }
+ }
+}
+
+// Goal of this test is to verify that unknown interface fails
+// to be parsed.
+TEST_F(Dhcp4ParserTest, unknownInterface) {
+
+ // Configuration string.
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ ],"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"interface\": \"ethX\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config, true));
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+}
+
+// The goal of this test is to check whether an option definition
+// that defines an option carrying an IPv4 address can be created.
+TEST_F(Dhcp4ParserTest, optionDefIpv4Address) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseOPTION_DEFS(config, true));
+ 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 = configureDhcp4Server(*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_IPV4_ADDRESS_TYPE, def->getType());
+ EXPECT_TRUE(def->getEncapsulatedSpace().empty());
+
+ // 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 DHCP4 or OPTION_DEFS parsers do not accept empty maps.
+ json.reset(new MapElement());
+ ASSERT_NO_THROW(status = configureDhcp4Server(*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(Dhcp4ParserTest, 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 = configureDhcp4Server(*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());
+ EXPECT_TRUE(def->getEncapsulatedSpace().empty());
+
+ // 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(Dhcp4ParserTest, 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 = configureDhcp4Server(*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());
+ EXPECT_TRUE(def1->getEncapsulatedSpace().empty());
+
+ // 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());
+ EXPECT_TRUE(def2->getEncapsulatedSpace().empty());
+}
+
+// The goal of this test is to verify that the duplicated option
+// definition is not accepted.
+TEST_F(Dhcp4ParserTest, 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 = configureDhcp4Server(*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(Dhcp4ParserTest, 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 = configureDhcp4Server(*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());
+ EXPECT_TRUE(def->getEncapsulatedSpace().empty());
+}
+
+// The purpose of this test to verify that encapsulated option
+// space name may be specified.
+TEST_F(Dhcp4ParserTest, 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 = configureDhcp4Server(*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(Dhcp4ParserTest, 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 = configureDhcp4Server(*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(Dhcp4ParserTest, 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 = configureDhcp4Server(*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(Dhcp4ParserTest, 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 = configureDhcp4Server(*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(Dhcp4ParserTest, 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 = configureDhcp4Server(*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(Dhcp4ParserTest, 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 = configureDhcp4Server(*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(Dhcp4ParserTest, 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 = configureDhcp4Server(*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(Dhcp4ParserTest, 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 = configureDhcp4Server(*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 dhcp4 option
+/// space and has its definition) and that it is allowed to define
+/// option in the dhcp4 option space that has a code which is not
+/// used by any of the standard options.
+TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
+
+ // Configuration string. The option code 109 is unassigned so it
+ // can be used for a custom option definition in dhcp4 option space.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 109,"
+ " \"type\": \"string\","
+ " \"space\": \"dhcp4\""
+ " } ]"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseOPTION_DEFS(config));
+ extractConfig(config);
+
+ OptionDefinitionPtr def = CfgMgr::instance().getStagingCfg()->
+ getCfgOptionDef()->get(DHCP4_OPTION_SPACE, 109);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getStagingCfg()->
+ getCfgOptionDef()->get(DHCP4_OPTION_SPACE, 109);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(109, def->getCode());
+ EXPECT_EQ(OPT_STRING_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+
+ // The combination of option space and code is invalid. The 'dhcp4' option
+ // space groups standard options and the code 3 is reserved for one of
+ // them.
+ config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"routers\","
+ " \"code\": 3,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"dhcp4\""
+ " } ]"
+ "}";
+ ASSERT_NO_THROW(json = parseOPTION_DEFS(config));
+
+ // Use the configuration string to create new option definition.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ /// There is no definition for unassigned option 170.
+ config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"unassigned-option-170\","
+ " \"code\": 170,"
+ " \"type\": \"string\","
+ " \"space\": \"dhcp4\""
+ " } ]"
+ "}";
+ ASSERT_NO_THROW(json = parseOPTION_DEFS(config));
+ extractConfig(config);
+
+ // Use the configuration string to create new option definition.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting success.
+ checkResult(status, 0);
+
+ def = CfgMgr::instance().getStagingCfg()->
+ getCfgOptionDef()->get(DHCP4_OPTION_SPACE, 170);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("unassigned-option-170", def->getName());
+ EXPECT_EQ(170, 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(Dhcp4ParserTest, optionDataDefaultsGlobal) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"data\": \"ABCDEF0105\","
+ " \"csv-format\": false"
+ " },"
+ " {"
+ " \"name\": \"default-ip-ttl\","
+ " \"data\": \"01\","
+ " \"csv-format\": false"
+ " } ],"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 0);
+
+ // These options are global
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
+ ASSERT_EQ(0, options->size());
+
+ options = CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll(DHCP4_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(56);
+ // Expect single option with the code equal to 56.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ const uint8_t foo_expected[] = {
+ 0xAB, 0xCD, 0xEF, 0x01, 0x05
+ };
+ // Check if option is valid in terms of code and carried data.
+ testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
+
+ range = idx.equal_range(23);
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ // Do another round of testing with second option.
+ const uint8_t foo2_expected[] = {
+ 0x01
+ };
+ testOption(*range.first, 23, foo2_expected, sizeof(foo2_expected));
+
+ // Check that options with other option codes are not returned.
+ for (uint8_t code = 24; code < 35; ++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(Dhcp4ParserTest, optionDataDefaultsSubnet) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"data\": \"ABCDEF0105\","
+ " \"csv-format\": false"
+ " },"
+ " {"
+ " \"name\": \"default-ip-ttl\","
+ " \"data\": \"01\","
+ " \"csv-format\": false"
+ " } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 0);
+
+ // These options are subnet options
+ OptionContainerPtr options =
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
+ ASSERT_EQ(0, options->size());
+
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+ options = subnet->getCfgOption()->getAll(DHCP4_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(56);
+ // Expect single option with the code equal to 56.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ const uint8_t foo_expected[] = {
+ 0xAB, 0xCD, 0xEF, 0x01, 0x05
+ };
+ // Check if option is valid in terms of code and carried data.
+ testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
+
+ range = idx.equal_range(23);
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ // Do another round of testing with second option.
+ const uint8_t foo2_expected[] = {
+ 0x01
+ };
+ testOption(*range.first, 23, foo2_expected, sizeof(foo2_expected));
+}
+
+/// 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(Dhcp4ParserTest, 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 'dhcp4' option space as it is the
+ // standard option.
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"valid-lifetime\": 4000,"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"data\": \"ABCDEF0105\","
+ " \"csv-format\": false"
+ " },"
+ " {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"data\": \"1234\""
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 56,"
+ " \"type\": \"uint32\","
+ " \"space\": \"isc\""
+ " } ],"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Options should be now available
+ // Try to get the option from the space dhcp4.
+ OptionDescriptor desc1 =
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->get(DHCP4_OPTION_SPACE, 56);
+ ASSERT_TRUE(desc1.option_);
+ EXPECT_EQ(56, desc1.option_->getType());
+ // Try to get the option from the space isc.
+ OptionDescriptor desc2 =
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->get("isc", 56);
+ ASSERT_TRUE(desc2.option_);
+ EXPECT_EQ(56, 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", 56);
+ 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(Dhcp4ParserTest, optionDataEncapsulate) {
+
+ // @todo DHCP configurations has many dependencies between
+ // parameters. First of all, configuration for subnet was
+ // inherited from the global values. Thus subnet had 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() + "," +
+ "\"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\": 1,"
+ " \"type\": \"uint32\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"code\": 2,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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 Kea
+ // configuration manager sends whole configuration for the lists
+ // where at least one element is being modified or added.
+ config = "{ " + genIfaceConfig() + "," +
+ "\"valid-lifetime\": 3000,"
+ "\"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\": 222,"
+ " \"type\": \"uint8\","
+ " \"space\": \"dhcp4\","
+ " \"encapsulate\": \"isc\""
+ "},"
+ "{"
+ " \"name\": \"foo\","
+ " \"code\": 1,"
+ " \"type\": \"uint32\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"code\": 2,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"isc\""
+ " } ],"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ]"
+ "}";
+
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // We should have one option available.
+ OptionContainerPtr options =
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
+ ASSERT_TRUE(options);
+ ASSERT_EQ(1, options->size());
+
+ // Get the option.
+ OptionDescriptor desc =
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->get(DHCP4_OPTION_SPACE, 222);
+ EXPECT_TRUE(desc.option_);
+ EXPECT_EQ(222, desc.option_->getType());
+
+ // This option should comprise two sub-options.
+ // One of them is 'foo' with code 1.
+ OptionPtr option_foo = desc.option_->getOption(1);
+ ASSERT_TRUE(option_foo);
+ EXPECT_EQ(1, option_foo->getType());
+
+ // ...another one 'foo2' with code 2.
+ OptionPtr option_foo2 = desc.option_->getOption(2);
+ ASSERT_TRUE(option_foo2);
+ EXPECT_EQ(2, option_foo2->getType());
+}
+
+// Goal of this test is to verify options configuration
+// for a single subnet. In particular this test checks
+// that local options configuration overrides global
+// option setting.
+TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"data\": \"AB\","
+ " \"csv-format\": false"
+ " } ],"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"data\": \"ABCDEF0105\","
+ " \"csv-format\": false"
+ " },"
+ " {"
+ " \"name\": \"default-ip-ttl\","
+ " \"data\": \"01\","
+ " \"csv-format\": false"
+ " } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 0);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.24"));
+ ASSERT_TRUE(subnet);
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP4_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(56);
+ // Expect single option with the code equal to 100.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ const uint8_t foo_expected[] = {
+ 0xAB, 0xCD, 0xEF, 0x01, 0x05
+ };
+ // Check if option is valid in terms of code and carried data.
+ testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
+
+ range = idx.equal_range(23);
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ // Do another round of testing with second option.
+ const uint8_t foo2_expected[] = {
+ 0x01
+ };
+ testOption(*range.first, 23, foo2_expected, sizeof(foo2_expected));
+}
+
+// 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(Dhcp4ParserTest, optionDataBoolean) {
+ // Create configuration. Use standard option 19 (ip-forwarding).
+ std::map<std::string, std::string> params;
+ params["name"] = "ip-forwarding";
+ params["space"] = DHCP4_OPTION_SPACE;
+ params["code"] = "19";
+ 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 19.
+ OptionDescriptor desc = getOptionFromSubnet(IOAddress("192.0.2.24"),
+ 19);
+ 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, 19, 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, 19, expected_option_data,
+ sizeof(expected_option_data));
+
+ // The value of "1" with a few leading zeros should work too.
+ params["data"] = "00001";
+ testConfiguration(params, 19, 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, 19, expected_option_data,
+ sizeof(expected_option_data));
+
+ // Specifying "0" should have the same effect as "false".
+ params["data"] = "0";
+ testConfiguration(params, 19, expected_option_data,
+ sizeof(expected_option_data));
+
+ // The same effect should be for multiple 0 chars.
+ params["data"] = "00000";
+ testConfiguration(params, 19, 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, 19, expected_option_data,
+ sizeof(expected_option_data));
+
+ // The binary 1 should work as well.
+ params["data"] = "1";
+ expected_option_data[0] = 1;
+ testConfiguration(params, 19, expected_option_data,
+ sizeof(expected_option_data));
+
+ // As well as an even number of digits.
+ params["data"] = "01";
+ testConfiguration(params, 19, expected_option_data,
+ sizeof(expected_option_data));
+
+}
+
+// Goal of this test is to verify options configuration
+// for multiple subnets.
+TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"data\": \"0102030405060708090A\","
+ " \"csv-format\": false"
+ " } ]"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.3.101 - 192.0.3.150\" } ],"
+ " \"subnet\": \"192.0.3.0/24\", "
+ " \"option-data\": [ {"
+ " \"name\": \"default-ip-ttl\","
+ " \"data\": \"FF\","
+ " \"csv-format\": false"
+ " } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 0);
+
+ Subnet4Ptr subnet1 = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.100"));
+ ASSERT_TRUE(subnet1);
+ OptionContainerPtr options1 = subnet1->getCfgOption()->getAll(DHCP4_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(56);
+ // Expect single option with the code equal to 56.
+ ASSERT_EQ(1, std::distance(range1.first, range1.second));
+ const uint8_t foo_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, 56, foo_expected, sizeof(foo_expected));
+
+ // Test another subnet in the same way.
+ Subnet4Ptr subnet2 = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(IOAddress("192.0.3.102"));
+ ASSERT_TRUE(subnet2);
+ OptionContainerPtr options2 = subnet2->getCfgOption()->getAll(DHCP4_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(23);
+ ASSERT_EQ(1, std::distance(range2.first, range2.second));
+
+ const uint8_t foo2_expected[] = { 0xFF };
+ testOption(*range2.first, 23, foo2_expected, sizeof(foo2_expected));
+}
+
+// This test verifies that it is possible to specify options on
+// pool levels.
+TEST_F(Dhcp4ParserTest, optionDataSinglePool) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + ","
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { "
+ " \"pool\": \"192.0.2.1 - 192.0.2.100\","
+ " \"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"data\": \"ABCDEF0105\","
+ " \"csv-format\": false"
+ " },"
+ " {"
+ " \"name\": \"default-ip-ttl\","
+ " \"data\": \"01\","
+ " \"csv-format\": false"
+ " } ]"
+ " } ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 0);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->
+ selectSubnet(IOAddress("192.0.2.24"), classify_);
+ ASSERT_TRUE(subnet);
+
+ PoolPtr pool = subnet->getPool(Lease::TYPE_V4, IOAddress("192.0.2.24"), false);
+ ASSERT_TRUE(pool);
+ Pool4Ptr pool4 = boost::dynamic_pointer_cast<Pool4>(pool);
+ ASSERT_TRUE(pool4);
+
+ OptionContainerPtr options =
+ pool4->getCfgOption()->getAll(DHCP4_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(56);
+ // Expect a single option with the code equal to 100.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ const uint8_t foo_expected[] = {
+ 0xAB, 0xCD, 0xEF, 0x01, 0x05
+ };
+ // Check if option is valid in terms of code and carried data.
+ testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
+
+ range = idx.equal_range(23);
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ // Do another round of testing with second option.
+ const uint8_t foo2_expected[] = {
+ 0x01
+ };
+ testOption(*range.first, 23, foo2_expected, sizeof(foo2_expected));
+}
+
+// This test verifies that it's possible to define different options in
+// different pools and those options are not confused.
+TEST_F(Dhcp4ParserTest, optionDataMultiplePools) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + ","
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { "
+ " \"pool\": \"192.0.2.1 - 192.0.2.100\","
+ " \"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"data\": \"ABCDEF0105\","
+ " \"csv-format\": false"
+ " } ]"
+ " },"
+ " {"
+ " \"pool\": \"192.0.2.200 - 192.0.2.250\","
+ " \"option-data\": [ {"
+ " \"name\": \"default-ip-ttl\","
+ " \"data\": \"01\","
+ " \"csv-format\": false"
+ " } ]"
+ " } ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 0);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->
+ selectSubnet(IOAddress("192.0.2.24"), classify_);
+ ASSERT_TRUE(subnet);
+
+ PoolPtr pool1 = subnet->getPool(Lease::TYPE_V4, IOAddress("192.0.2.24"), false);
+ ASSERT_TRUE(pool1);
+ Pool4Ptr pool41 = boost::dynamic_pointer_cast<Pool4>(pool1);
+ ASSERT_TRUE(pool41);
+
+ OptionContainerPtr options1 =
+ pool41->getCfgOption()->getAll(DHCP4_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(56);
+ // Expect a single option with the code equal to 100.
+ ASSERT_EQ(1, std::distance(range1.first, range1.second));
+ const uint8_t foo_expected[] = {
+ 0xAB, 0xCD, 0xEF, 0x01, 0x05
+ };
+ // Check if option is valid in terms of code and carried data.
+ testOption(*range1.first, 56, foo_expected, sizeof(foo_expected));
+
+ // Test another pool in the same way.
+ PoolPtr pool2 = subnet->getPool(Lease::TYPE_V4, IOAddress("192.0.2.240"), false);
+ ASSERT_TRUE(pool2);
+ Pool4Ptr pool42 = boost::dynamic_pointer_cast<Pool4>(pool2);
+ ASSERT_TRUE(pool42);
+
+ OptionContainerPtr options2 =
+ pool42->getCfgOption()->getAll(DHCP4_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(23);
+ ASSERT_EQ(1, std::distance(range2.first, range2.second));
+ const uint8_t foo2_expected[] = {
+ 0x01
+ };
+ testOption(*range2.first, 23, foo2_expected, sizeof(foo2_expected));
+}
+
+// Verify that empty option name is rejected in the configuration.
+TEST_F(Dhcp4ParserTest, optionNameEmpty) {
+ // Empty option names not allowed.
+ testInvalidOptionParam("", "name");
+}
+
+// Verify that empty option name with spaces is rejected
+// in the configuration.
+TEST_F(Dhcp4ParserTest, optionNameSpaces) {
+ // Spaces in option names not allowed.
+ testInvalidOptionParam("option foo", "name");
+}
+
+// Verify that negative option code is rejected in the configuration.
+TEST_F(Dhcp4ParserTest, 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(Dhcp4ParserTest, optionCodeNonUint8) {
+ // The valid option codes are uint16_t values so passing
+ // uint16_t maximum value incremented by 1 should result
+ // in failure.
+ testInvalidOptionParam("257", "code");
+}
+
+// Verify that zero option code is rejected in the configuration.
+TEST_F(Dhcp4ParserTest, 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(Dhcp4ParserTest, 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(Dhcp4ParserTest, 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 = parseDHCP4(config));
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 0);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.5"));
+ ASSERT_TRUE(subnet);
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP4_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(56);
+ // Expect single option with the code equal to 100.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ const uint8_t foo_expected[] = { 0x0A, 0x0B, 0x0C, 0x0D };
+
+ // Check if option is valid in terms of code and carried data.
+ testOption(*range.first, 56, foo_expected, sizeof(foo_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(Dhcp4ParserTest, stdOptionData) {
+ ConstElementPtr x;
+ std::map<std::string, std::string> params;
+ params["name"] = "nis-servers";
+ params["space"] = DHCP4_OPTION_SPACE;
+ // Option code 41 means nis-servers.
+ params["code"] = "41";
+ // Specify option values in a CSV (user friendly) format.
+ params["data"] = "192.0.2.10, 192.0.2.1, 192.0.2.3";
+ params["csv-format"] = "true";
+
+ std::string config = createConfigWithOption(params);
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 0);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.5"));
+ ASSERT_TRUE(subnet);
+ OptionContainerPtr options =
+ subnet->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
+ ASSERT_TRUE(options);
+ 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(DHO_NIS_SERVERS);
+ // Expect single option with the code equal to NIS_SERVERS option code.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ // The actual pointer to the option is held in the option field
+ // in the structure returned.
+ OptionPtr option = range.first->option_;
+ ASSERT_TRUE(option);
+ // Option object returned for here is expected to be Option6IA
+ // which is derived from Option. This class is dedicated to
+ // represent standard option IA_NA.
+ boost::shared_ptr<Option4AddrLst> option_addrs =
+ boost::dynamic_pointer_cast<Option4AddrLst>(option);
+ // If cast is unsuccessful than option returned was of a
+ // different type than Option6IA. This is wrong.
+ ASSERT_TRUE(option_addrs);
+
+ // Get addresses from the option.
+ Option4AddrLst::AddressContainer addrs = option_addrs->getAddresses();
+ // Verify that the addresses have been configured correctly.
+ ASSERT_EQ(3, addrs.size());
+ EXPECT_EQ("192.0.2.10", addrs[0].toText());
+ EXPECT_EQ("192.0.2.1", addrs[1].toText());
+ EXPECT_EQ("192.0.2.3", addrs[2].toText());
+}
+
+/// This test checks if Uint32Parser can really parse the whole range
+/// and properly err of out of range values. As we can't call Uint32Parser
+/// directly, we are exploiting the fact that it is used to parse global
+/// parameter renew-timer and the results are stored in uint32_defaults.
+/// We get the uint32_defaults using a getUint32Defaults functions which
+/// is defined only to access the values from this test.
+TEST_F(Dhcp4ParserTest, DISABLED_Uint32Parser) {
+
+ ConstElementPtr status;
+
+ // CASE 1: 0 - minimum value, should work
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_,
+ parseDHCP4("{\"renew-timer\": 0}")));
+
+ // returned value must be ok (0 is a proper value)
+ checkResult(status, 0);
+ /// @todo: check that the renew-timer is really 0
+
+ // CASE 2: 4294967295U (UINT_MAX) should work as well
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_,
+ parseDHCP4("{\"renew-timer\": 4294967295}")));
+
+ // returned value must be ok (0 is a proper value)
+ checkResult(status, 0);
+ /// @todo: check that the renew-timer is really 4294967295U
+
+ // CASE 3: 4294967296U (UINT_MAX + 1) should not work
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_,
+ parseJSON("{\"renew-timer\": 4294967296}")));
+
+ // returned value must be rejected (1 configuration error)
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ // CASE 4: -1 (UINT_MIN -1 ) should not work
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_,
+ parseJSON("{\"renew-timer\": -1}")));
+
+ // returned value must be rejected (1 configuration error)
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+}
+
+// The goal of this test is to verify that the domain-search option
+// can be set using domain names
+TEST_F(Dhcp4ParserTest, domainSearchOption) {
+ // Create configuration.
+ std::map<std::string, std::string> params;
+ params["name"] = "domain-search";
+ params["space"] = DHCP4_OPTION_SPACE;
+ params["code"] = "119"; // DHO_DOMAIN_SEARCH
+ params["data"] = "mydomain.example.com, example.com";
+ params["csv-format"] = "true";
+
+ std::string config = createConfigWithOption(params);
+ EXPECT_TRUE(executeConfiguration(config, "parse configuration with a"
+ " domain-search option"));
+}
+
+// The goal of this test is to verify that the slp-directory-agent
+// option can be set using a trailing array of addresses and
+// slp-service-scope without option scope list
+TEST_F(Dhcp4ParserTest, slpOptions) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"slp-directory-agent\","
+ " \"data\": \"true, 10.0.0.3, 127.0.0.1\""
+ " },"
+ " {"
+ " \"name\": \"slp-service-scope\","
+ " \"data\": \"false, \""
+ " } ],"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config, true));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 0);
+
+ // Get options
+ OptionContainerPtr options = CfgMgr::instance().getStagingCfg()->
+ getCfgOption()->getAll(DHCP4_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(DHO_DIRECTORY_AGENT);
+ // Expect a single option with the code equal to 78.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ const uint8_t sda_expected[] = {
+ 0x01, 0x0a, 0x00, 0x00, 0x03, 0x7f, 0x00, 0x00, 0x01
+ };
+ // Check if option is valid in terms of code and carried data.
+ testOption(*range.first, 78, sda_expected, sizeof(sda_expected));
+
+ range = idx.equal_range(DHO_SERVICE_SCOPE);
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ // Do another round of testing with second option.
+ const uint8_t sss_expected[] = {
+ 0x00
+ };
+ testOption(*range.first, 79, sss_expected, sizeof(sss_expected));
+}
+
+// The goal of this test is to verify that the standard option can
+// be configured to encapsulate multiple other options.
+TEST_F(Dhcp4ParserTest, 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() + "," +
+ "\"valid-lifetime\": 4000,"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"vendor-encapsulated-options-space\","
+ " \"data\": \"1234\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"space\": \"vendor-encapsulated-options-space\","
+ " \"data\": \"192.168.2.1\""
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 1,"
+ " \"type\": \"uint32\","
+ " \"space\": \"vendor-encapsulated-options-space\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"code\": 2,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"vendor-encapsulated-options-space\""
+ " } ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ CfgMgr::instance().clear();
+
+ // Once the definitions have been added we can configure the
+ // standard option #43. This option comprises an enterprise
+ // number and sub options. By convention (introduced in
+ // std_option_defs.h) option named 'vendor-encapsulated-options'
+ // encapsulates the option space named 'vendor-encapsulated-options-space'.
+ // We add our dummy options to this option space and thus
+ // they should be included as sub-options in the
+ // 'vendor-encapsulated-options' option.
+ config = "{ " + genIfaceConfig() + "," +
+ "\"valid-lifetime\": 3000,"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"vendor-encapsulated-options\","
+ " \"csv-format\": false"
+ " },"
+ " {"
+ " \"name\": \"foo\","
+ " \"space\": \"vendor-encapsulated-options-space\","
+ " \"data\": \"1234\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"space\": \"vendor-encapsulated-options-space\","
+ " \"code\": 2,"
+ " \"data\": \"192.168.2.1\","
+ " \"csv-format\": true"
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 1,"
+ " \"type\": \"uint32\","
+ " \"space\": \"vendor-encapsulated-options-space\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"code\": 2,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"vendor-encapsulated-options-space\""
+ " } ],"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ]"
+ "}";
+
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // We should have one option available.
+ OptionContainerPtr options =
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
+ ASSERT_TRUE(options);
+ ASSERT_EQ(1, options->size());
+
+ // Get the option.
+ OptionDescriptor desc = CfgMgr::instance().getStagingCfg()->
+ getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ EXPECT_TRUE(desc.option_);
+ EXPECT_EQ(DHO_VENDOR_ENCAPSULATED_OPTIONS, desc.option_->getType());
+
+ // Option with the code 1 should be added as a sub-option.
+ OptionPtr option_foo = desc.option_->getOption(1);
+ ASSERT_TRUE(option_foo);
+ EXPECT_EQ(1, 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 2 should be added as a sub-option.
+ OptionPtr option_foo2 = desc.option_->getOption(2);
+ ASSERT_TRUE(option_foo2);
+ EXPECT_EQ(2, 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 3 should not be added.
+ EXPECT_FALSE(desc.option_->getOption(3));
+}
+
+// This test checks if vendor options can be specified in the config file
+// (in hex format), and later retrieved
+TEST_F(Dhcp4ParserTest, 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() + "," +
+ "\"valid-lifetime\": 4000,"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"option-one\","
+ " \"space\": \"vendor-4491\"," // VENDOR_ID_CABLE_LABS = 4491
+ " \"code\": 100," // just a random code
+ " \"data\": \"ABCDEF0105\","
+ " \"csv-format\": false"
+ " },"
+ " {"
+ " \"name\": \"option-two\","
+ " \"space\": \"vendor-1234\","
+ " \"code\": 100,"
+ " \"data\": \"1234\","
+ " \"csv-format\": false"
+ " } ],"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1-192.0.2.10\" } ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // 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, 100);
+ ASSERT_FALSE(desc3.option_);
+}
+
+// This test checks if vendor options can be specified in the config file,
+// (in csv format), and later retrieved
+TEST_F(Dhcp4ParserTest, 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() + "," +
+ "\"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\""
+ " } ],"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" "
+ " } ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // 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_);
+}
+
+// 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) {
+
+ // 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 += (string("{ \"library\": \"") + libraries[i] + string("\" }"));
+ }
+
+ // Append the remainder of the configuration.
+ config += string(
+ "],"
+ "\"valid-lifetime\": 4000,"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"data\": \"ABCDEF0105\","
+ " \"csv-format\": false"
+ " },"
+ " {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"data\": \"1234\""
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 56,"
+ " \"type\": \"uint32\","
+ " \"space\": \"isc\""
+ " } ],"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ]");
+
+ if (multi_threading) {
+ config += string(
+ ","
+ "\"multi-threading\": {"
+ " \"enable-multi-threading\": true"
+ "}");
+ }
+
+ config += string("}");
+
+ return (config);
+}
+
+// Convenience function for creating hooks library configuration with one or
+// two character string constants.
+std::string
+buildHooksLibrariesConfig(const char* library1 = NULL,
+ const char* library2 = NULL) {
+ std::vector<std::string> libraries;
+ if (library1 != NULL) {
+ libraries.push_back(string(library1));
+ if (library2 != NULL) {
+ libraries.push_back(string(library2));
+ }
+ }
+ return (buildHooksLibrariesConfig(libraries, false));
+}
+
+// The goal of this test is to verify the configuration of hooks libraries if
+// none are specified.
+TEST_F(Dhcp4ParserTest, 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(Dhcp4ParserTest, InvalidLibrary) {
+ // Parse a configuration containing a failing library.
+ string config = buildHooksLibrariesConfig(NOT_PRESENT_LIBRARY);
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = configureDhcp4Server(*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(Dhcp4ParserTest, 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);
+ 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));
+
+ // Commit the changes so as we get the fresh configuration for the
+ // second part of this test.
+ 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(Dhcp4ParserTest, 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 = parseDHCP4(config));
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = configureDhcp4Server(*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(Dhcp4ParserTest, 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 = parseDHCP4(config));
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = configureDhcp4Server(*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(Dhcp4ParserTest, selectedInterfaces) {
+ IfaceMgrTestConfig test_config(true);
+
+ ConstElementPtr x;
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"eth0\", \"eth1\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+
+ // Make sure the config manager is clean and there is no hanging
+ // interface configuration.
+ EXPECT_FALSE(test_config.socketOpen("eth0", AF_INET));
+ EXPECT_FALSE(test_config.socketOpen("eth1", AF_INET));
+
+ // Apply configuration.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ CfgMgr::instance().getStagingCfg()->getCfgIface()->openSockets(AF_INET, 10000);
+
+ // eth0 and eth1 were explicitly selected. eth2 was not.
+ EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET));
+ EXPECT_TRUE(test_config.socketOpen("eth1", AF_INET));
+}
+
+// This test verifies that it is possible to configure the server in such a way
+// that it listens on all interfaces.
+TEST_F(Dhcp4ParserTest, allInterfaces) {
+ IfaceMgrTestConfig test_config(true);
+
+ ConstElementPtr x;
+ // This configuration specifies two interfaces on which server should listen
+ // but it also includes asterisk. The asterisk 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\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+
+ // Make sure there is no old configuration.
+ ASSERT_FALSE(test_config.socketOpen("eth0", AF_INET));
+ ASSERT_FALSE(test_config.socketOpen("eth1", AF_INET));
+
+ // Apply configuration.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ CfgMgr::instance().getStagingCfg()->getCfgIface()->openSockets(AF_INET, 10000);
+
+ // All interfaces should be now active.
+ ASSERT_TRUE(test_config.socketOpen("eth0", AF_INET));
+ ASSERT_TRUE(test_config.socketOpen("eth1", AF_INET));
+}
+
+// This test verifies that it is possible to select subset of interfaces
+// and addresses.
+TEST_F(Dhcp4ParserTest, selectedInterfacesAndAddresses) {
+ IfaceMgrTestConfig test_config(true);
+
+ ConstElementPtr x;
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"eth0/10.0.0.1\", \"eth1/192.0.2.3\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+
+ ConstElementPtr status;
+
+ // Make sure the config manager is clean and there is no hanging
+ // interface configuration.
+ ASSERT_FALSE(test_config.socketOpen("eth0", "10.0.0.1"));
+ ASSERT_FALSE(test_config.socketOpen("eth1", "192.0.2.3"));
+ ASSERT_FALSE(test_config.socketOpen("eth1", "192.0.2.5"));
+
+ // Apply configuration.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ CfgMgr::instance().getStagingCfg()->getCfgIface()->openSockets(AF_INET, 10000);
+
+ // An address on eth0 was selected
+ EXPECT_TRUE(test_config.socketOpen("eth0", "10.0.0.1"));
+ // The 192.0.2.3 address on eth1 was selected.
+ EXPECT_TRUE(test_config.socketOpen("eth1", "192.0.2.3"));
+ // The 192.0.2.5 was not selected, thus the socket should not
+ // be bound to this address.
+ EXPECT_FALSE(test_config.socketOpen("eth1", "192.0.2.5"));
+}
+
+// This test verifies that valid d2CliengConfig works correctly.
+TEST_F(Dhcp4ParserTest, d2ClientConfigValid) {
+ ConstElementPtr status;
+
+ // 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() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ " \"dhcp-ddns\" : {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"192.168.2.1\", "
+ " \"server-port\" : 777, "
+ " \"sender-ip\" : \"192.168.2.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 = parseDHCP4(config_str, true));
+ extractConfig(config_str);
+
+ // Pass the configuration in for parsing.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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("192.168.2.1", d2_client_config->getServerIp().toText());
+ EXPECT_EQ(777, d2_client_config->getServerPort());
+ EXPECT_EQ("192.168.2.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(Dhcp4ParserTest, d2ClientConfigMoveToGlobal) {
+ ConstElementPtr status;
+
+ // 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() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ " \"dhcp-ddns\" : {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"192.168.2.1\", "
+ " \"server-port\" : 777, "
+ " \"sender-ip\" : \"192.168.2.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 = parseDHCP4(config_str, true));
+ extractConfig(config_str);
+
+ // Pass the configuration in for parsing.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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("192.168.2.1", d2_client_config->getServerIp().toText());
+ EXPECT_EQ(777, d2_client_config->getServerPort());
+ EXPECT_EQ("192.168.2.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(Dhcp4ParserTest, d2ClientConfigBoth) {
+ ConstElementPtr status;
+
+ // 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() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ " \"dhcp-ddns\" : {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"192.168.2.1\", "
+ " \"server-port\" : 777, "
+ " \"sender-ip\" : \"192.168.2.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 = parseDHCP4(config_str, true));
+ extractConfig(config_str);
+
+ // Pass the configuration in for parsing.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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("192.168.2.1", d2_client_config->getServerIp().toText());
+ EXPECT_EQ(777, d2_client_config->getServerPort());
+ EXPECT_EQ("192.168.2.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(Dhcp4ParserTest, invalidD2ClientConfig) {
+ ConstElementPtr status;
+
+ // Configuration string with an invalid D2 client config,
+ // "server-ip" is invalid.
+ string config_str = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ " \"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 = parseDHCP4(config_str));
+
+ // Configuration should not throw, but should fail.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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());
+}
+
+// This test checks if it is possible to specify relay information
+TEST_F(Dhcp4ParserTest, subnetRelayInfo) {
+
+ ConstElementPtr status;
+
+ // A config with relay information.
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"renew-timer\": 1, "
+ " \"rebind-timer\": 2, "
+ " \"valid-lifetime\": 4,"
+ " \"relay\": { "
+ " \"ip-address\": \"192.0.2.123\""
+ " },"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+ // returned value should be 0 (configuration success)
+ checkResult(status, 0);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+
+ EXPECT_TRUE(subnet->hasRelays());
+ EXPECT_TRUE(subnet->hasRelayAddress(IOAddress("192.0.2.123")));
+}
+
+// This test checks if it is possible to specify a list of relays
+TEST_F(Dhcp4ParserTest, subnetRelayInfoList) {
+
+ ConstElementPtr status;
+
+ // A config with relay information.
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"renew-timer\": 1, "
+ " \"rebind-timer\": 2, "
+ " \"valid-lifetime\": 4,"
+ " \"relay\": { "
+ " \"ip-addresses\": [ \"192.0.3.123\", \"192.0.3.124\" ]"
+ " },"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+ // returned value should be 0 (configuration success)
+ checkResult(status, 0);
+
+ SubnetSelector selector;
+ selector.giaddr_ = IOAddress("192.0.2.200");
+
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(selector);
+ ASSERT_TRUE(subnet);
+
+ EXPECT_TRUE(subnet->hasRelays());
+ EXPECT_TRUE(subnet->hasRelayAddress(IOAddress("192.0.3.123")));
+ EXPECT_TRUE(subnet->hasRelayAddress(IOAddress("192.0.3.124")));
+}
+
+// Goal of this test is to verify that multiple subnets can be configured
+// with defined client classes.
+TEST_F(Dhcp4ParserTest, classifySubnets) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"client-class\": \"alpha\" "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.3.101 - 192.0.3.150\" } ],"
+ " \"subnet\": \"192.0.3.0/24\", "
+ " \"client-class\": \"beta\" "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
+ " \"subnet\": \"192.0.4.0/24\", "
+ " \"client-class\": \"gamma\" "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.5.101 - 192.0.5.150\" } ],"
+ " \"subnet\": \"192.0.5.0/24\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 0);
+
+ const Subnet4Collection* subnets =
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->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(Dhcp4ParserTest, classifyPools) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { "
+ " \"pool\": \"192.0.2.1 - 192.0.2.100\", "
+ " \"client-class\": \"alpha\" "
+ " },"
+ " {"
+ " \"pool\": \"192.0.3.101 - 192.0.3.150\", "
+ " \"client-class\": \"beta\" "
+ " },"
+ " {"
+ " \"pool\": \"192.0.4.101 - 192.0.4.150\", "
+ " \"client-class\": \"gamma\" "
+ " },"
+ " {"
+ " \"pool\": \"192.0.5.101 - 192.0.5.150\" "
+ " } ],"
+ " \"subnet\": \"192.0.0.0/16\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config, true));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 0);
+
+ const Subnet4Collection* subnets =
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(1, subnets->size());
+ const PoolCollection& pools = (*subnets->begin())->getPools(Lease::TYPE_V4);
+ 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 pools[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 the host reservations can be specified for
+// respective IPv4 subnets.
+TEST_F(Dhcp4ParserTest, reservations) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ "
+ " { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"id\": 123,"
+ " \"reservations\": ["
+ " ]"
+ " },"
+ " {"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ " \"ip-address\": \"192.0.3.112\","
+ " \"hostname\": \"\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"name-servers\","
+ " \"data\": \"192.0.3.15\""
+ " },"
+ " {"
+ " \"name\": \"default-ip-ttl\","
+ " \"data\": \"32\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"hw-address\": \"01:02:03:04:05:06\","
+ " \"ip-address\": \"192.0.3.120\","
+ " \"hostname\": \"\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"name-servers\","
+ " \"data\": \"192.0.3.95\""
+ " },"
+ " {"
+ " \"name\": \"default-ip-ttl\","
+ " \"data\": \"11\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"pools\": [ { \"pool\": \"192.0.3.101 - 192.0.3.150\" } ],"
+ " \"subnet\": \"192.0.3.0/24\", "
+ " \"id\": 234"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
+ " \"subnet\": \"192.0.4.0/24\","
+ " \"id\": 542,"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"0A:09:08:07:06:05:04:03:02:01\","
+ " \"ip-address\": \"192.0.4.101\","
+ " \"hostname\": \"\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"name-servers\","
+ " \"data\": \"192.0.4.11\""
+ " },"
+ " {"
+ " \"name\": \"default-ip-ttl\","
+ " \"data\": \"95\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"circuit-id\": \"060504030201\","
+ " \"ip-address\": \"192.0.4.102\","
+ " \"hostname\": \"\""
+ " },"
+ " {"
+ " \"client-id\": \"05:01:02:03:04:05:06\","
+ " \"ip-address\": \"192.0.4.103\","
+ " \"hostname\": \"\""
+ " }"
+ " ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*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 Subnet4Collection* subnets =
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->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->get4(234, Host::IDENT_HWADDR,
+ &hwaddr[0], hwaddr.size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ("192.0.3.120", host->getIPv4Reservation().toText());
+ // This reservation should be solely assigned to the subnet 234,
+ // and not to other two.
+ EXPECT_FALSE(hosts_cfg->get4(123, Host::IDENT_HWADDR,
+ &hwaddr[0], hwaddr.size()));
+ EXPECT_FALSE(hosts_cfg->get4(542, Host::IDENT_HWADDR,
+ &hwaddr[0], hwaddr.size()));
+ // Check that options are assigned correctly.
+ Option4AddrLstPtr opt_dns =
+ retrieveOption<Option4AddrLstPtr>(*host, DHO_NAME_SERVERS);
+ ASSERT_TRUE(opt_dns);
+ Option4AddrLst::AddressContainer dns_addrs = opt_dns->getAddresses();
+ ASSERT_EQ(1, dns_addrs.size());
+ EXPECT_EQ("192.0.3.95", dns_addrs[0].toText());
+ OptionUint8Ptr opt_ttl =
+ retrieveOption<OptionUint8Ptr>(*host, DHO_DEFAULT_IP_TTL);
+ ASSERT_TRUE(opt_ttl);
+ EXPECT_EQ(11, static_cast<int>(opt_ttl->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->get4(234, Host::IDENT_DUID, &duid[0], duid.size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ("192.0.3.112", host->getIPv4Reservation().toText());
+ EXPECT_FALSE(hosts_cfg->get4(123, Host::IDENT_DUID, &duid[0], duid.size()));
+ EXPECT_FALSE(hosts_cfg->get4(542, Host::IDENT_DUID, &duid[0], duid.size()));
+ // Check that options are assigned correctly.
+ opt_dns = retrieveOption<Option4AddrLstPtr>(*host, DHO_NAME_SERVERS);
+ ASSERT_TRUE(opt_dns);
+ dns_addrs = opt_dns->getAddresses();
+ ASSERT_EQ(1, dns_addrs.size());
+ EXPECT_EQ("192.0.3.15", dns_addrs[0].toText());
+ opt_ttl = retrieveOption<OptionUint8Ptr>(*host, DHO_DEFAULT_IP_TTL);
+ ASSERT_TRUE(opt_ttl);
+ EXPECT_EQ(32, static_cast<int>(opt_ttl->getValue()));
+
+ // The circuit-id 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> circuit_id(hwaddr.rbegin(), hwaddr.rend());
+ host = hosts_cfg->get4(542, Host::IDENT_CIRCUIT_ID, &circuit_id[0],
+ circuit_id.size());
+ EXPECT_TRUE(host);
+ EXPECT_EQ("192.0.4.102", host->getIPv4Reservation().toText());
+ // This reservation must not belong to other subnets.
+ EXPECT_FALSE(hosts_cfg->get4(123, Host::IDENT_CIRCUIT_ID,
+ &circuit_id[0], circuit_id.size()));
+ EXPECT_FALSE(hosts_cfg->get4(234, Host::IDENT_CIRCUIT_ID,
+ &circuit_id[0], circuit_id.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->get4(542, Host::IDENT_DUID, &duid_r[0], duid_r.size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ("192.0.4.101", host->getIPv4Reservation().toText());
+ EXPECT_FALSE(hosts_cfg->get4(123, Host::IDENT_DUID,
+ &duid_r[0], duid_r.size()));
+ EXPECT_FALSE(hosts_cfg->get4(234, Host::IDENT_DUID,
+ &duid_r[0], duid_r.size()));
+ // Check that options are assigned correctly.
+ opt_dns = retrieveOption<Option4AddrLstPtr>(*host, DHO_NAME_SERVERS);
+ ASSERT_TRUE(opt_dns);
+ dns_addrs = opt_dns->getAddresses();
+ ASSERT_EQ(1, dns_addrs.size());
+ EXPECT_EQ("192.0.4.11", dns_addrs[0].toText());
+ opt_ttl = retrieveOption<OptionUint8Ptr>(*host, DHO_DEFAULT_IP_TTL);
+ ASSERT_TRUE(opt_ttl);
+ EXPECT_EQ(95, static_cast<int>(opt_ttl->getValue()));
+
+ // Check that we can find reservation using client identifier.
+ ClientIdPtr client_id = ClientId::fromText("05:01:02:03:04:05:06");
+ host = hosts_cfg->get4(542, Host::IDENT_CLIENT_ID, &client_id->getDuid()[0],
+ client_id->getDuid().size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ("192.0.4.103", host->getIPv4Reservation().toText());
+
+ // But this reservation should not be returned for other subnet.
+ host = hosts_cfg->get4(234, Host::IDENT_CLIENT_ID, &client_id->getDuid()[0],
+ client_id->getDuid().size());
+ EXPECT_FALSE(host);
+}
+
+// This test checks that it is possible to configure option data for a
+// host using a user defined option format.
+TEST_F(Dhcp4ParserTest, 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\""
+ "} ],"
+ "\"subnet4\": [ "
+ " {"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ " \"ip-address\": \"192.0.3.112\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"foo\","
+ " \"data\": \"123\","
+ " \"space\": \"isc\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"pools\": [ { \"pool\": \"192.0.3.101 - 192.0.3.150\" } ],"
+ " \"subnet\": \"192.0.3.0/24\", "
+ " \"id\": 234"
+ " } ],"
+ "\"valid-lifetime\": 4000"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config, true));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*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->get4(234, Host::IDENT_DUID, &duid[0], duid.size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ("192.0.3.112", host->getIPv4Reservation().toText());
+
+ // 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(123, opt_foo->getValue());
+}
+
+// This test verifies that the bogus host reservation would trigger a
+// server configuration error.
+TEST_F(Dhcp4ParserTest, reservationBogus) {
+ // Case 1: misspelled hw-address parameter.
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ "
+ " { "
+ " \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
+ " \"subnet\": \"192.0.4.0/24\","
+ " \"id\": 542,"
+ " \"reservations\": ["
+ " {"
+ " \"hw-addre\": \"06:05:04:03:02:01\","
+ " \"ip-address\": \"192.0.4.102\","
+ " \"hostname\": \"\""
+ " }"
+ " ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseJSON(config));
+
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 1);
+
+ EXPECT_THROW(parseDHCP4(config), Dhcp4ParseError);
+
+ // Case 2: DUID and HW Address both specified.
+ config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ "
+ " { "
+ " \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
+ " \"subnet\": \"192.0.4.0/24\","
+ " \"id\": 542,"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"01:02:03:04:05:06\","
+ " \"hw-address\": \"06:05:04:03:02:01\","
+ " \"ip-address\": \"192.0.4.102\","
+ " \"hostname\": \"\""
+ " }"
+ " ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+
+ // Remove existing configuration, if any.
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 1);
+
+ // Case 3: Neither ip address nor hostname specified.
+ config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ "
+ " { "
+ " \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
+ " \"subnet\": \"192.0.4.0/24\","
+ " \"id\": 542,"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"06:05:04:03:02:01\""
+ " }"
+ " ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+
+ // Remove existing configuration, if any.
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 1);
+
+ // Case 4: Broken specification of option data.
+ config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ "
+ " { "
+ " \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
+ " \"subnet\": \"192.0.4.0/24\","
+ " \"id\": 542,"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"06:05:04:03:02:01\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"name-servers\","
+ " \"data\": \"bogus-ip-address\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+
+ // Remove existing configuration, if any.
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 1);
+}
+
+/// The goal of this test is to verify that Host Reservation flags can be
+/// specified on a per-subnet basis.
+TEST_F(Dhcp4ParserTest, hostReservationPerSubnet) {
+
+ /// - Configuration:
+ /// - only addresses (no prefixes)
+ /// - 7 subnets with:
+ /// - 192.0.1.0/24 (all reservations enabled)
+ /// - 192.0.2.0/24 (out-of-pool reservations)
+ /// - 192.0.3.0/24 (reservations disabled)
+ /// - 192.0.4.0/24 (global reservations)
+ /// - 192.0.5.0/24 (reservations not specified)
+ /// - 192.0.6.0/24 (global + all enabled)
+ /// - 192.0.7.0/24 (global + out-of-pool enabled)
+ const char* hr_config =
+ "{ "
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.1.0/24\" } ],"
+ " \"subnet\": \"192.0.1.0/24\", "
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": false"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.2.0/24\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": true"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.3.0/24\" } ],"
+ " \"subnet\": \"192.0.3.0/24\", "
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": false"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.4.0/24\" } ],"
+ " \"subnet\": \"192.0.4.0/24\", "
+ " \"reservations-global\": true,"
+ " \"reservations-in-subnet\": false"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.5.0/24\" } ],"
+ " \"subnet\": \"192.0.5.0/24\""
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.6.0/24\" } ],"
+ " \"subnet\": \"192.0.6.0/24\", "
+ " \"reservations-global\": true,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": false"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.7.0/24\" } ],"
+ " \"subnet\": \"192.0.7.0/24\", "
+ " \"reservations-global\": true,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": true"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(hr_config));
+ extractConfig(hr_config);
+ ConstElementPtr result;
+ EXPECT_NO_THROW(result = configureDhcp4Server(*srv_, json));
+
+ // returned value should be 0 (success)
+ checkResult(result, 0);
+
+ // Let's get all subnets and check that there are 7 of them.
+ ConstCfgSubnets4Ptr subnets = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+ ASSERT_TRUE(subnets);
+ const Subnet4Collection* 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
+ Subnet4Ptr subnet;
+ subnet = subnets->selectSubnet(IOAddress("192.0.1.1"));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getReservationsGlobal());
+ EXPECT_TRUE(subnet->getReservationsInSubnet());
+ EXPECT_FALSE(subnet->getReservationsOutOfPool());
+
+ // Subnet 2
+ subnet = subnets->selectSubnet(IOAddress("192.0.2.1"));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getReservationsGlobal());
+ EXPECT_TRUE(subnet->getReservationsInSubnet());
+ EXPECT_TRUE(subnet->getReservationsOutOfPool());
+
+ // Subnet 3
+ subnet = subnets->selectSubnet(IOAddress("192.0.3.1"));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getReservationsGlobal());
+ EXPECT_FALSE(subnet->getReservationsInSubnet());
+ EXPECT_FALSE(subnet->getReservationsOutOfPool());
+
+ // Subnet 4
+ subnet = subnets->selectSubnet(IOAddress("192.0.4.1"));
+ ASSERT_TRUE(subnet);
+ EXPECT_TRUE(subnet->getReservationsGlobal());
+ EXPECT_FALSE(subnet->getReservationsInSubnet());
+ EXPECT_FALSE(subnet->getReservationsOutOfPool());
+
+ // Subnet 5
+ subnet = subnets->selectSubnet(IOAddress("192.0.5.1"));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getReservationsGlobal());
+ EXPECT_TRUE(subnet->getReservationsInSubnet());
+ EXPECT_FALSE(subnet->getReservationsOutOfPool());
+
+ // Subnet 6
+ subnet = subnets->selectSubnet(IOAddress("192.0.6.1"));
+ ASSERT_TRUE(subnet);
+ EXPECT_TRUE(subnet->getReservationsGlobal());
+ EXPECT_TRUE(subnet->getReservationsInSubnet());
+ EXPECT_FALSE(subnet->getReservationsOutOfPool());
+
+ // Subnet 7
+ subnet = subnets->selectSubnet(IOAddress("192.0.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(Dhcp4ParserTest, hostReservationGlobal) {
+
+ /// - Configuration:
+ /// - only addresses (no prefixes)
+ /// - 2 subnets with :
+ /// - 192.0.2.0/24 (all reservations enabled)
+ /// - 192.0.3.0/24 (reservations not specified)
+ const char* hr_config =
+ "{ "
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"reservations-global\": false,"
+ "\"reservations-in-subnet\": true,"
+ "\"reservations-out-of-pool\": true,"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.0/24\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": false"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.3.0/24\" } ],"
+ " \"subnet\": \"192.0.3.0/24\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(hr_config));
+ extractConfig(hr_config);
+ ConstElementPtr result;
+ EXPECT_NO_THROW(result = configureDhcp4Server(*srv_, json));
+
+ // returned value should be 0 (success)
+ checkResult(result, 0);
+
+ // Let's get all subnets and check that there are 4 of them.
+ ConstCfgSubnets4Ptr subnets = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+ ASSERT_TRUE(subnets);
+ const Subnet4Collection* 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
+ Subnet4Ptr subnet;
+ subnet = subnets->selectSubnet(IOAddress("192.0.2.1"));
+ ASSERT_TRUE(subnet);
+ // Reset the fetch global function to staging (vs current) config.
+ subnet->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getStagingCfg()->getConfiguredGlobals());
+ });
+ EXPECT_FALSE(subnet->getReservationsGlobal());
+ EXPECT_TRUE(subnet->getReservationsInSubnet());
+ EXPECT_FALSE(subnet->getReservationsOutOfPool());
+
+ // Subnet 2
+ subnet = subnets->selectSubnet(IOAddress("192.0.3.1"));
+ ASSERT_TRUE(subnet);
+ // Reset the fetch global function to staging (vs current) config.
+ subnet->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getStagingCfg()->getConfiguredGlobals());
+ });
+ EXPECT_FALSE(subnet->getReservationsGlobal());
+ EXPECT_TRUE(subnet->getReservationsInSubnet());
+ EXPECT_TRUE(subnet->getReservationsOutOfPool());
+}
+
+/// Check that the decline-probation-period has a default value when not
+/// specified.
+TEST_F(Dhcp4ParserTest, declineTimerDefault) {
+ ConstElementPtr status;
+
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"subnet4\": [ ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+ // 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 GLOBAL4_DEFAULTS in
+ // simple_parser4.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(Dhcp4ParserTest, dhcp4o6portDefault) {
+
+ string config_txt = "{ " + genIfaceConfig() + ","
+ "\"subnet4\": [ ] "
+ "}";
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCP4(config_txt));
+ extractConfig(config_txt);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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 GLOBAL4_DEFAULTS in
+ // simple_parser4.cc.
+ EXPECT_EQ(0, CfgMgr::instance().getStagingCfg()->getDhcp4o6Port());
+}
+
+/// Check that the decline-probation-period value can be set properly.
+TEST_F(Dhcp4ParserTest, declineTimer) {
+ ConstElementPtr status;
+
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"decline-probation-period\": 12345,"
+ "\"subnet4\": [ ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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(Dhcp4ParserTest, declineTimerError) {
+ ConstElementPtr status;
+
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"decline-probation-period\": \"soon\","
+ "\"subnet4\": [ ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseJSON(config));
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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 Dhcp4 parser catches the type error
+ EXPECT_THROW(parseDHCP4(config), Dhcp4ParseError);
+}
+
+// Check that configuration for the expired leases processing may be
+// specified.
+TEST_F(Dhcp4ParserTest, 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"
+ "},"
+ "\"subnet4\": [ ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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(Dhcp4ParserTest, 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"
+ "},"
+ "\"subnet4\": [ ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+ // Returned value should be 0 (error)
+ checkResult(status, 1);
+
+ // Check that the error contains error position.
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+}
+
+// Checks if the DHCPv4 is able to parse the configuration without 4o6 parameters
+// and does not set 4o6 fields at all.
+TEST_F(Dhcp4ParserTest, 4o6default) {
+
+ ConstElementPtr status;
+
+ // Just a plain v4 config (no 4o6 parameters)
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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.
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+
+ Cfg4o6& dhcp4o6 = subnet->get4o6();
+ EXPECT_FALSE(dhcp4o6.enabled());
+}
+
+// Checks if the DHCPv4 is able to parse the configuration with 4o6 subnet
+// defined.
+TEST_F(Dhcp4ParserTest, 4o6subnet) {
+
+ ConstElementPtr status;
+
+ // Just a plain v4 config (no 4o6 parameters)
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"4o6-subnet\": \"2001:db8::123/45\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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.
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+
+ Cfg4o6& dhcp4o6 = subnet->get4o6();
+ EXPECT_TRUE(dhcp4o6.enabled());
+ EXPECT_EQ(IOAddress("2001:db8::123"), dhcp4o6.getSubnet4o6().get().first);
+ EXPECT_EQ(45, dhcp4o6.getSubnet4o6().get().second);
+}
+
+// Checks if the DHCPv4 is able to parse the configuration with 4o6 subnet
+// defined.
+TEST_F(Dhcp4ParserTest, 4o6subnetBogus) {
+
+ ConstElementPtr status;
+
+ // Just a plain v4 config (no 4o6 parameters)
+ string config[] = {
+ // Bogus configuration 1: missing / in subnet
+ "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"4o6-subnet\": \"2001:db8::123\" } ],"
+ "\"valid-lifetime\": 4000 }",
+
+ // Bogus configuration 2: incorrect address
+ "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"4o6-subnet\": \"2001:db8:bogus/45\" } ],"
+ "\"valid-lifetime\": 4000 }",
+
+ // Bogus configuration 3: incorrect prefix length
+ "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"4o6-subnet\": \"2001:db8::123/200\" } ],"
+ "\"valid-lifetime\": 4000 }"
+ };
+
+ ConstElementPtr json1;
+ ASSERT_NO_THROW(json1 = parseDHCP4(config[0]));
+ ConstElementPtr json2;
+ ASSERT_NO_THROW(json2 = parseDHCP4(config[0]));
+ ConstElementPtr json3;
+ ASSERT_NO_THROW(json3 = parseDHCP4(config[0]));
+
+ // Check that the first config is rejected.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json1));
+ checkResult(status, 1);
+
+ // Check that the second config is rejected.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json2));
+ checkResult(status, 1);
+
+ // Check that the third config is rejected.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json3));
+ checkResult(status, 1);
+}
+
+// Checks if the DHCPv4 is able to parse the configuration with 4o6 network
+// interface defined.
+TEST_F(Dhcp4ParserTest, 4o6iface) {
+
+ ConstElementPtr status;
+
+ // Just a plain v4 config (no 4o6 parameters)
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"4o6-interface\": \"ethX\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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.
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+
+ Cfg4o6& dhcp4o6 = subnet->get4o6();
+ EXPECT_TRUE(dhcp4o6.enabled());
+ EXPECT_EQ("ethX", dhcp4o6.getIface4o6().get());
+}
+
+// Checks if the DHCPv4 is able to parse the configuration with both 4o6 network
+// interface and v6 subnet defined.
+TEST_F(Dhcp4ParserTest, 4o6subnetIface) {
+
+ ConstElementPtr status;
+
+ // Just a plain v4 config (no 4o6 parameters)
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"4o6-subnet\": \"2001:db8::543/21\","
+ " \"4o6-interface\": \"ethX\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+ // check if returned status is OK
+ checkResult(status, 0);
+
+ // Now check if the configuration was indeed handled and we have
+ // expected subnet configured...
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+
+ // ... and that subnet has 4o6 network interface specified.
+ Cfg4o6& dhcp4o6 = subnet->get4o6();
+ EXPECT_TRUE(dhcp4o6.enabled());
+ EXPECT_EQ(IOAddress("2001:db8::543"), dhcp4o6.getSubnet4o6().get().first);
+ EXPECT_EQ(21, dhcp4o6.getSubnet4o6().get().second);
+ EXPECT_EQ("ethX", dhcp4o6.getIface4o6().get());
+}
+
+// Checks if the DHCPv4 is able to parse the configuration with 4o6 network
+// interface-id.
+TEST_F(Dhcp4ParserTest, 4o6subnetInterfaceId) {
+
+ ConstElementPtr status;
+
+ // Just a plain v4 config (no 4o6 parameters)
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"4o6-interface-id\": \"vlan123\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+ // check if returned status is OK
+ checkResult(status, 0);
+
+ // Now check if the configuration was indeed handled and we have
+ // expected 4o6-interface-id configured.
+ Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+ getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+
+ Cfg4o6& dhcp4o6 = subnet->get4o6();
+ EXPECT_TRUE(dhcp4o6.enabled());
+ OptionPtr ifaceid = dhcp4o6.getInterfaceId();
+ ASSERT_TRUE(ifaceid);
+
+ vector<uint8_t> data = ifaceid->getData();
+ EXPECT_EQ(7, data.size());
+ const char *exp = "vlan123";
+ EXPECT_EQ(0, memcmp(&data[0], exp, data.size()));
+}
+
+// Verifies that simple list of valid classes parses and
+// is staged for commit.
+TEST_F(Dhcp4ParserTest, validClientClassDictionary) {
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"valid-lifetime\": 4000, \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"
+ "\"subnet4\": [ { \n"
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], \n"
+ " \"subnet\": \"192.0.2.0/24\" \n"
+ " } ] \n"
+ "} \n";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*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(Dhcp4ParserTest, 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"
+ "\"subnet4\": [ { \n"
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], \n"
+ " \"subnet\": \"192.0.2.0/24\" \n"
+ " } ] \n"
+ "} \n";
+
+ EXPECT_THROW(parseDHCP4(config), Dhcp4ParseError);
+}
+
+// Verifies that simple list of valid classes parses and
+// is staged for commit.
+TEST_F(Dhcp4ParserTest, clientClassValidLifetime) {
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"client-classes\" : [ \n"
+ " { \n"
+ " \"name\": \"one\", \n"
+ " \"min-valid-lifetime\": 1000, \n"
+ " \"valid-lifetime\": 2000, \n"
+ " \"max-valid-lifetime\": 3000 \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"two\" \n"
+ " } \n"
+ "], \n"
+ "\"subnet4\": [ { \n"
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], \n"
+ " \"subnet\": \"192.0.2.0/24\" \n"
+ " } ] \n"
+ "} \n";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW_LOG(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW_LOG(status = configureDhcp4Server(*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);
+
+ class_def = dictionary->findClass("two");
+ ASSERT_TRUE(class_def);
+ EXPECT_TRUE(class_def->getValid().unspecified());
+}
+
+
+// Test verifies that regular configuration does not provide any user context
+// in the address pool.
+TEST_F(Dhcp4ParserTest, poolUserContextMissing) {
+ extractConfig(PARSER_CONFIGS[0]);
+ PoolPtr pool;
+ getPool(string(PARSER_CONFIGS[0]), 0, 0, 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(Dhcp4ParserTest, poolUserContextEmpty) {
+ extractConfig(PARSER_CONFIGS[1]);
+ PoolPtr pool;
+ getPool(string(PARSER_CONFIGS[1]), 0, 0, 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(Dhcp4ParserTest, poolUserContextData) {
+ extractConfig(PARSER_CONFIGS[2]);
+ PoolPtr pool;
+ getPool(string(PARSER_CONFIGS[2]), 0, 0, 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(3, ctx->size());
+ ConstElementPtr int_param = ctx->get("integer-param");
+ ConstElementPtr str_param = ctx->get("string-param");
+ ConstElementPtr bool_param = ctx->get("bool-param");
+
+ ASSERT_TRUE(int_param);
+ ASSERT_EQ(Element::integer, int_param->getType());
+ int64_t int_value;
+ EXPECT_NO_THROW(int_param->getValue(int_value));
+ EXPECT_EQ(42L, int_value);
+
+ ASSERT_TRUE(str_param);
+ ASSERT_EQ(Element::string, str_param->getType());
+ EXPECT_EQ("Sagittarius", str_param->stringValue());
+
+ ASSERT_TRUE(bool_param);
+ bool bool_value;
+ ASSERT_EQ(Element::boolean, bool_param->getType());
+ EXPECT_NO_THROW(bool_param->getValue(bool_value));
+ EXPECT_TRUE(bool_value);
+}
+
+// Test verifies that it's possible to specify parameters in the user context
+// in the min-max address pool.
+TEST_F(Dhcp4ParserTest, pooMinMaxlUserContext) {
+ extractConfig(PARSER_CONFIGS[3]);
+ PoolPtr pool;
+ getPool(string(PARSER_CONFIGS[3]), 0, 0, 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(3, ctx->size());
+ ConstElementPtr int_param = ctx->get("integer-param");
+ ConstElementPtr str_param = ctx->get("string-param");
+ ConstElementPtr bool_param = ctx->get("bool-param");
+
+ ASSERT_TRUE(int_param);
+ ASSERT_EQ(Element::integer, int_param->getType());
+ int64_t int_value;
+ EXPECT_NO_THROW(int_param->getValue(int_value));
+ EXPECT_EQ(42L, int_value);
+
+ ASSERT_TRUE(str_param);
+ ASSERT_EQ(Element::string, str_param->getType());
+ EXPECT_EQ("Sagittarius", str_param->stringValue());
+
+ ASSERT_TRUE(bool_param);
+ bool bool_value;
+ ASSERT_EQ(Element::boolean, bool_param->getType());
+ EXPECT_NO_THROW(bool_param->getValue(bool_value));
+ EXPECT_TRUE(bool_value);
+}
+
+// Test verifies the error message for an incorrect pool range
+// is what we expect.
+TEST_F(Dhcp4ParserTest, invalidPoolRange) {
+ string config = "{ " + genIfaceConfig() + ", \n" +
+ "\"valid-lifetime\": 4000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet4\": [ { \n"
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 19.2.0.200\" } ], \n"
+ " \"subnet\": \"192.0.2.0/24\" \n"
+ " } ] \n"
+ "} \n";
+
+ string expected = "Failed to create pool defined by: "
+ "192.0.2.1-19.2.0.200 (<string>:6:26)";
+
+ configure(config, CONTROL_RESULT_ERROR, expected);
+}
+
+// Test verifies the error message for an outside subnet pool range
+// is what we expect.
+TEST_F(Dhcp4ParserTest, outsideSubnetPool) {
+ string config = "{ " + genIfaceConfig() + ", \n" +
+ "\"valid-lifetime\": 4000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet4\": [ { \n"
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], \n"
+ " \"subnet\": \"10.0.2.0/24\" \n"
+ " } ] \n"
+ "} \n";
+
+ string expected = "subnet configuration failed: "
+ "a pool of type V4, with the following address range: "
+ "192.0.2.1-192.0.2.100 does not match the prefix of a subnet: "
+ "10.0.2.0/24 to which it is being added (<string>:5:14)";
+
+ configure(config, CONTROL_RESULT_ERROR, expected);
+}
+
+// Test verifies that empty shared networks are accepted.
+TEST_F(Dhcp4ParserTest, sharedNetworksEmpty) {
+ string config = "{\n"
+ "\"valid-lifetime\": 4000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet4\": [ { \n"
+ " \"pools\": [ { \"pool\": \"10.0.2.1 - 10.0.2.100\" } ], \n"
+ " \"subnet\": \"10.0.2.0/24\" \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(Dhcp4ParserTest, sharedNetworksNoName) {
+ string config = "{\n"
+ "\"valid-lifetime\": 4000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet4\": [ { \n"
+ " \"pools\": [ { \"pool\": \"10.0.2.1 - 10.0.2.100\" } ], \n"
+ " \"subnet\": \"10.0.2.0/24\" \n"
+ " } ],\n"
+ "\"shared-networks\": [ { } ]\n"
+ "} \n";
+
+ EXPECT_THROW(parseDHCP4(config, true), Dhcp4ParseError);
+}
+
+// Test verifies that empty shared networks are accepted.
+TEST_F(Dhcp4ParserTest, sharedNetworksEmptyName) {
+ string config = "{\n"
+ "\"valid-lifetime\": 4000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet4\": [ { \n"
+ " \"pools\": [ { \"pool\": \"10.0.2.1 - 10.0.2.100\" } ], \n"
+ " \"subnet\": \"10.0.2.0/24\" \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(Dhcp4ParserTest, sharedNetworksName) {
+ string config = "{\n"
+ "\"subnet4\": [ { \n"
+ " \"pools\": [ { \"pool\": \"10.0.2.1 - 10.0.2.100\" } ], \n"
+ " \"subnet\": \"10.0.2.0/24\" \n"
+ " } ],\n"
+ "\"shared-networks\": [ { \"name\": \"foo\" } ]\n"
+ "} \n";
+
+ configure(config, CONTROL_RESULT_SUCCESS, "");
+
+ // Now verify that the shared network was indeed configured.
+ CfgSharedNetworks4Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+ ->getCfgSharedNetworks4();
+ ASSERT_TRUE(cfg_net);
+ const SharedNetwork4Collection* nets = cfg_net->getAll();
+ ASSERT_TRUE(nets);
+ ASSERT_EQ(1, nets->size());
+ SharedNetwork4Ptr net = *(nets->begin());
+ ASSERT_TRUE(net);
+ EXPECT_EQ("foo", net->getName());
+
+ // Verify that there are no subnets in this shared-network
+ const Subnet4SimpleCollection* subs = net->getAllSubnets();
+ ASSERT_TRUE(subs);
+ EXPECT_EQ(0, subs->size());
+}
+
+// Test verifies that a degenerated shared-network (just one subnet) is
+// accepted.
+TEST_F(Dhcp4ParserTest, sharedNetworks1subnet) {
+ string config = "{\n"
+ "\"valid-lifetime\": 4000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"shared-networks\": [ {\n"
+ " \"name\": \"foo\"\n,"
+ " \"subnet4\": [ { \n"
+ " \"subnet\": \"192.0.2.0/24\",\n"
+ " \"pools\": [ { \"pool\": \"192.0.2.1-192.0.2.10\" } ]\n"
+ " } ]\n"
+ " } ]\n"
+ "} \n";
+
+ configure(config, CONTROL_RESULT_SUCCESS, "");
+
+ // Now verify that the shared network was indeed configured.
+ CfgSharedNetworks4Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+ ->getCfgSharedNetworks4();
+ ASSERT_TRUE(cfg_net);
+
+ // There should be exactly one shared subnet.
+ const SharedNetwork4Collection* nets = cfg_net->getAll();
+ ASSERT_TRUE(nets);
+ ASSERT_EQ(1, nets->size());
+
+ SharedNetwork4Ptr net = *(nets->begin());
+ ASSERT_TRUE(net);
+ EXPECT_EQ("foo", net->getName());
+
+ // It should have one subnet.
+ const Subnet4SimpleCollection* subs = net->getAllSubnets();
+ ASSERT_TRUE(subs);
+ EXPECT_EQ(1, subs->size());
+ checkSubnet(*subs, "192.0.2.0/24", 1000, 2000, 4000);
+
+ // Now make sure the subnet was added to global list of subnets.
+ CfgSubnets4Ptr subnets4 = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+ ASSERT_TRUE(subnets4);
+
+ const Subnet4Collection* gsubs = subnets4->getAll();
+ ASSERT_TRUE(gsubs);
+ checkSubnet(*gsubs, "192.0.2.0/24", 1000, 2000, 4000);
+}
+
+// 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(Dhcp4ParserTest, sharedNetworks3subnets) {
+ string config = "{\n"
+ "\"valid-lifetime\": 4000, \n"
+ "\"min-valid-lifetime\": 3000, \n"
+ "\"max-valid-lifetime\": 5000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"shared-networks\": [ {\n"
+ " \"name\": \"foo\"\n,"
+ " \"subnet4\": [\n"
+ " { \n"
+ " \"subnet\": \"192.0.1.0/24\",\n"
+ " \"pools\": [ { \"pool\": \"192.0.1.1-192.0.1.10\" } ]\n"
+ " },\n"
+ " { \n"
+ " \"subnet\": \"192.0.2.0/24\",\n"
+ " \"pools\": [ { \"pool\": \"192.0.2.1-192.0.2.10\" } ],\n"
+ " \"renew-timer\": 2,\n"
+ " \"rebind-timer\": 22,\n"
+ " \"valid-lifetime\": 222,\n"
+ " \"min-valid-lifetime\": 111,\n"
+ " \"max-valid-lifetime\": 333\n"
+ " },\n"
+ " { \n"
+ " \"subnet\": \"192.0.3.0/24\",\n"
+ " \"pools\": [ { \"pool\": \"192.0.3.1-192.0.3.10\" } ]\n"
+ " }\n"
+ " ]\n"
+ " } ]\n"
+ "} \n";
+
+ configure(config, CONTROL_RESULT_SUCCESS, "");
+
+ // Now verify that the shared network was indeed configured.
+ CfgSharedNetworks4Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+ ->getCfgSharedNetworks4();
+
+ // There is expected one shared subnet.
+ ASSERT_TRUE(cfg_net);
+ const SharedNetwork4Collection* nets = cfg_net->getAll();
+ ASSERT_TRUE(nets);
+ ASSERT_EQ(1, nets->size());
+
+ SharedNetwork4Ptr net = *(nets->begin());
+ ASSERT_TRUE(net);
+
+ EXPECT_EQ("foo", net->getName());
+
+ const Subnet4SimpleCollection* subs = net->getAllSubnets();
+ ASSERT_TRUE(subs);
+ EXPECT_EQ(3, subs->size());
+ checkSubnet(*subs, "192.0.1.0/24", 1000, 2000, 4000, 3000, 5000);
+ checkSubnet(*subs, "192.0.2.0/24", 2, 22, 222, 111, 333);
+ checkSubnet(*subs, "192.0.3.0/24", 1000, 2000, 4000, 3000, 5000);
+
+ // Now make sure the subnet was added to global list of subnets.
+ CfgSubnets4Ptr subnets4 = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+ ASSERT_TRUE(subnets4);
+
+ const Subnet4Collection* gsubs = subnets4->getAll();
+ ASSERT_TRUE(gsubs);
+ checkSubnet(*gsubs, "192.0.1.0/24", 1000, 2000, 4000, 3000, 5000);
+ checkSubnet(*gsubs, "192.0.2.0/24", 2, 22, 222, 111, 333);
+ checkSubnet(*gsubs, "192.0.3.0/24", 1000, 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(Dhcp4ParserTest, 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);
+
+ // This config is structured in a way that the first shared
+ // subnet have many parameters defined. The first subnet
+ // should inherit them. The second subnet overrides all
+ // values and those values should be used, not those from
+ // shared network scope.
+ string config = "{\n"
+ "\"renew-timer\": 1, \n" // global values here
+ "\"rebind-timer\": 2, \n"
+ "\"valid-lifetime\": 4, \n"
+ "\"min-valid-lifetime\": 3, \n"
+ "\"max-valid-lifetime\": 5, \n"
+ "\"shared-networks\": [ {\n"
+ " \"name\": \"foo\"\n," // shared network values here
+ " \"interface\": \"eth0\",\n"
+ " \"match-client-id\": false,\n"
+ " \"authoritative\": true,\n"
+ " \"next-server\": \"1.2.3.4\",\n"
+ " \"server-hostname\": \"foo\",\n"
+ " \"boot-file-name\": \"bar\",\n"
+ " \"store-extended-info\": true,\n"
+ " \"relay\": {\n"
+ " \"ip-address\": \"5.6.7.8\"\n"
+ " },\n"
+ " \"reservations-global\": false,\n"
+ " \"reservations-in-subnet\": false,\n"
+ " \"renew-timer\": 10,\n"
+ " \"rebind-timer\": 20,\n"
+ " \"valid-lifetime\": 40,\n"
+ " \"min-valid-lifetime\": 30,\n"
+ " \"max-valid-lifetime\": 50,\n"
+ " \"subnet4\": [\n"
+ " { \n"
+ " \"subnet\": \"192.0.1.0/24\",\n"
+ " \"pools\": [ { \"pool\": \"192.0.1.1-192.0.1.10\" } ]\n"
+ " },\n"
+ " { \n"
+ " \"subnet\": \"192.0.2.0/24\",\n"
+ " \"pools\": [ { \"pool\": \"192.0.2.1-192.0.2.10\" } ],\n"
+ " \"renew-timer\": 100,\n"
+ " \"rebind-timer\": 200,\n"
+ " \"valid-lifetime\": 400,\n"
+ " \"min-valid-lifetime\": 300,\n"
+ " \"max-valid-lifetime\": 500,\n"
+ " \"match-client-id\": true,\n"
+ " \"next-server\": \"11.22.33.44\",\n"
+ " \"server-hostname\": \"some-name.example.org\",\n"
+ " \"boot-file-name\": \"bootfile.efi\",\n"
+ " \"relay\": {\n"
+ " \"ip-address\": \"55.66.77.88\"\n"
+ " },\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"
+ " \"subnet4\": [\n"
+ " {\n"
+ " \"subnet\": \"192.0.3.0/24\",\n"
+ " \"pools\": [ { \"pool\": \"192.0.3.1-192.0.3.10\" } ]\n"
+ " }\n"
+ " ]\n"
+ " } ]\n"
+ "} \n";
+
+ configure(config, CONTROL_RESULT_SUCCESS, "");
+
+ // Now verify that the shared network was indeed configured.
+ CfgSharedNetworks4Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+ ->getCfgSharedNetworks4();
+
+ // Two shared networks are expected.
+ ASSERT_TRUE(cfg_net);
+ const SharedNetwork4Collection* nets = cfg_net->getAll();
+ ASSERT_TRUE(nets);
+ ASSERT_EQ(2, nets->size());
+
+ SharedNetwork4Ptr net = nets->at(0);
+ ASSERT_TRUE(net);
+
+ // The first shared network has two subnets.
+ const Subnet4SimpleCollection* 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
+ // subnet4 level.
+ Subnet4Ptr s = checkSubnet(*subs, "192.0.1.0/24", 10, 20, 40, 30, 50);
+ ASSERT_TRUE(s);
+
+ // These are values derived from shared network scope:
+ EXPECT_EQ("eth0", s->getIface().get());
+ EXPECT_FALSE(s->getMatchClientId());
+ EXPECT_TRUE(s->getAuthoritative());
+ EXPECT_TRUE(s->getStoreExtendedInfo());
+ EXPECT_EQ(IOAddress("1.2.3.4"), s->getSiaddr());
+ EXPECT_EQ("foo", s->getSname().get());
+ EXPECT_EQ("bar", s->getFilename().get());
+ EXPECT_TRUE(s->hasRelayAddress(IOAddress("5.6.7.8")));
+ EXPECT_FALSE(s->getReservationsGlobal());
+ EXPECT_FALSE(s->getReservationsInSubnet());
+ EXPECT_FALSE(s->getReservationsOutOfPool());
+
+ // 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
+ // subnet4 level.
+ s = checkSubnet(*subs, "192.0.2.0/24", 100, 200, 400, 300, 500);
+
+ // These are values derived from shared network scope:
+ EXPECT_EQ("eth0", s->getIface().get());
+ EXPECT_TRUE(s->getMatchClientId());
+ EXPECT_TRUE(s->getAuthoritative());
+ EXPECT_TRUE(s->getStoreExtendedInfo());
+ EXPECT_EQ(IOAddress("11.22.33.44"), s->getSiaddr().get());
+ EXPECT_EQ("some-name.example.org", s->getSname().get());
+ EXPECT_EQ("bootfile.efi", s->getFilename().get());
+ EXPECT_TRUE(s->hasRelayAddress(IOAddress("55.66.77.88")));
+ EXPECT_FALSE(s->getReservationsGlobal());
+ EXPECT_TRUE(s->getReservationsInSubnet());
+ EXPECT_TRUE(s->getReservationsOutOfPool());
+
+ // 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.
+ // All other parameters should have default values.
+ s = checkSubnet(*subs, "192.0.3.0/24", 1, 2, 4, 3, 5);
+ EXPECT_EQ("", s->getIface().get());
+ EXPECT_TRUE(s->getMatchClientId());
+ EXPECT_FALSE(s->getAuthoritative());
+ EXPECT_FALSE(s->getStoreExtendedInfo());
+ EXPECT_EQ(IOAddress("0.0.0.0"), s->getSiaddr());
+ EXPECT_TRUE(s->getSname().empty());
+ EXPECT_TRUE(s->getFilename().empty());
+ EXPECT_FALSE(s->hasRelays());
+ EXPECT_FALSE(s->getReservationsGlobal());
+ EXPECT_TRUE(s->getReservationsInSubnet());
+ EXPECT_FALSE(s->getReservationsOutOfPool());
+}
+
+// This test checks if client-class is derived properly.
+TEST_F(Dhcp4ParserTest, sharedNetworksDeriveClientClass) {
+
+ // This config is structured in a way that the first shared network has
+ // client-class defined. This should in general be inherited by subnets, but
+ // it's also possible to override the values on subnet level.
+ string config = "{\n"
+ "\"renew-timer\": 1, \n" // global values here
+ "\"rebind-timer\": 2, \n"
+ "\"valid-lifetime\": 4, \n"
+ "\"shared-networks\": [ {\n"
+ " \"name\": \"foo\"\n," // shared network values here
+ " \"client-class\": \"alpha\",\n"
+ " \"subnet4\": [\n"
+ " { \n"
+ " \"subnet\": \"192.0.1.0/24\",\n"
+ " \"pools\": [ { \"pool\": \"192.0.1.1-192.0.1.10\" } ]\n"
+ " },\n"
+ " { \n"
+ " \"subnet\": \"192.0.2.0/24\",\n"
+ " \"pools\": [ { \"pool\": \"192.0.2.1-192.0.2.10\" } ],\n"
+ " \"client-class\": \"beta\"\n"
+ " }\n"
+ " ]\n"
+ " },\n"
+ "{ // second shared-network starts here\n"
+ " \"name\": \"bar\",\n"
+ " \"subnet4\": [\n"
+ " {\n"
+ " \"subnet\": \"192.0.3.0/24\",\n"
+ " \"pools\": [ { \"pool\": \"192.0.3.1-192.0.3.10\" } ]\n"
+ " }\n"
+ " ]\n"
+
+ " } ]\n"
+ "} \n";
+
+ configure(config, CONTROL_RESULT_SUCCESS, "");
+
+ // Now verify that the shared network was indeed configured.
+ CfgSharedNetworks4Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+ ->getCfgSharedNetworks4();
+
+ // Two shared networks are expected.
+ ASSERT_TRUE(cfg_net);
+ const SharedNetwork4Collection* nets = cfg_net->getAll();
+ ASSERT_TRUE(nets);
+ ASSERT_EQ(2, nets->size());
+
+ SharedNetwork4Ptr net = nets->at(0);
+ ASSERT_TRUE(net);
+
+ EXPECT_EQ("alpha", net->getClientClass().get());
+
+ // The first shared network has two subnets.
+ const Subnet4SimpleCollection* 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.
+ Subnet4Ptr s = checkSubnet(*subs, "192.0.1.0/24", 1, 2, 4);
+ 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, "192.0.2.0/24", 1, 2, 4);
+ 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());
+
+ s = checkSubnet(*subs, "192.0.3.0/24", 1, 2, 4);
+ EXPECT_TRUE(s->getClientClass().empty());
+}
+
+// This test checks multiple host data sources.
+TEST_F(Dhcp4ParserTest, hostsDatabases) {
+
+ string config = PARSER_CONFIGS[4];
+ 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(Dhcp4ParserTest, comments) {
+
+ string config = PARSER_CONFIGS[5];
+ 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 DHCPv4 server\"", ctx->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_IPV4_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(DHCP4_OPTION_SPACE, DHO_DHCP_MESSAGE);
+ ASSERT_TRUE(opt_desc.option_);
+ EXPECT_EQ(DHO_DHCP_MESSAGE, 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/kea4-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 CfgSharedNetworks4Ptr& cfg_net =
+ CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks4();
+ ASSERT_TRUE(cfg_net);
+ const SharedNetwork4Collection* nets = cfg_net->getAll();
+ ASSERT_TRUE(nets);
+ ASSERT_EQ(1, nets->size());
+ SharedNetwork4Ptr 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 Subnet4SimpleCollection* subs = net->getAllSubnets();
+ ASSERT_TRUE(subs);
+ ASSERT_EQ(1, subs->size());
+ Subnet4Ptr sub = *subs->begin();
+ ASSERT_TRUE(sub);
+ EXPECT_EQ(100, sub->getID());
+ EXPECT_EQ("192.0.1.0/24", sub->toText());
+
+ // 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());
+
+ // The subnet has a pool.
+ const PoolCollection& pools = sub->getPools(Lease::TYPE_V4);
+ 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 host reservation.
+ uint8_t hw[] = { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
+ ConstHostPtr host =
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->
+ get4(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(100, host->getIPv4SubnetID());
+ EXPECT_EQ(SUBNET_ID_UNUSED, 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->getCfgOption4();
+ ASSERT_TRUE(opts);
+ EXPECT_FALSE(opts->empty());
+ const OptionDescriptor& host_desc =
+ opts->get(DHCP4_OPTION_SPACE, DHO_DOMAIN_NAME);
+ ASSERT_TRUE(host_desc.option_);
+ EXPECT_EQ(DHO_DOMAIN_NAME, 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());
+
+#if 0
+ // Loggers section supports comments too.
+
+ string logging = "{\n"
+ "\"loggers\": [ {\n"
+ " \"comment\": \"A logger\",\n"
+ " \"name\": \"kea-dhcp4\"\n"
+ "} ]\n";
+#endif
+}
+
+// This test verifies that the global host reservations can be specified
+TEST_F(Dhcp4ParserTest, globalReservations) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000,\n"
+ "\"reservations\": [\n"
+ " {\n"
+ " \"duid\": \"01:02:03:04:05:06:07:08:09:0A\",\n"
+ " \"ip-address\": \"192.0.200.1\",\n"
+ " \"hostname\": \"global1\",\n"
+ " \"option-data\": [\n"
+ " {\n"
+ " \"name\": \"name-servers\",\n"
+ " \"data\": \"192.0.3.15\"\n"
+ " },\n"
+ " {\n"
+ " \"name\": \"default-ip-ttl\",\n"
+ " \"data\": \"32\"\n"
+ " }\n"
+ " ]\n"
+ " },\n"
+ " {\n"
+ " \"hw-address\": \"01:02:03:04:05:06\",\n"
+ " \"hostname\": \"global2\",\n"
+ " \"option-data\": [\n"
+ " {\n"
+ " \"name\": \"name-servers\",\n"
+ " \"data\": \"192.0.3.95\"\n"
+ " },\n"
+ " {\n"
+ " \"name\": \"default-ip-ttl\",\n"
+ " \"data\": \"11\"\n"
+ " }\n"
+ " ]\n"
+ " }],\n"
+ "\"subnet4\": [ \n"
+ " { \n"
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],\n"
+ " \"subnet\": \"192.0.2.0/24\", \n"
+ " \"id\": 123,\n"
+ " \"reservations\": [\n"
+ " ]\n"
+ " },\n"
+ " {\n"
+ " \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],\n"
+ " \"subnet\": \"192.0.4.0/24\",\n"
+ " \"id\": 542\n"
+ " } ],\n"
+ "\"valid-lifetime\": 4000"
+ "}\n";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*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 Subnet4Collection* subnets =
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(2, subnets->size());
+
+ // Hosts configuration must be available.
+ CfgHostsPtr hosts_cfg = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_TRUE(hosts_cfg);
+
+ // Let's create a hardware address of the host named "global2"
+ std::vector<uint8_t> hwaddr;
+ for (unsigned int i = 1; i < 7; ++i) {
+ hwaddr.push_back(static_cast<uint8_t>(i));
+ }
+
+ // Retrieve the global reservation and sanity check the hostname reserved.
+ ConstHostPtr host = hosts_cfg->get4(SUBNET_ID_GLOBAL, Host::IDENT_HWADDR,
+ &hwaddr[0], hwaddr.size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ("global2", host->getHostname());
+
+ // Check that options are stored correctly.
+ Option4AddrLstPtr opt_dns =
+ retrieveOption<Option4AddrLstPtr>(*host, DHO_NAME_SERVERS);
+ ASSERT_TRUE(opt_dns);
+ Option4AddrLst::AddressContainer dns_addrs = opt_dns->getAddresses();
+ ASSERT_EQ(1, dns_addrs.size());
+ EXPECT_EQ("192.0.3.95", dns_addrs[0].toText());
+ OptionUint8Ptr opt_ttl =
+ retrieveOption<OptionUint8Ptr>(*host, DHO_DEFAULT_IP_TTL);
+ ASSERT_TRUE(opt_ttl);
+ EXPECT_EQ(11, static_cast<int>(opt_ttl->getValue()));
+
+ // This reservation should be global solely and not assigned to
+ // either subnet
+ EXPECT_FALSE(hosts_cfg->get4(123, Host::IDENT_HWADDR,
+ &hwaddr[0], hwaddr.size()));
+
+ EXPECT_FALSE(hosts_cfg->get4(542, Host::IDENT_HWADDR,
+ &hwaddr[0], hwaddr.size()));
+
+ // 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));
+ }
+
+ // Retrieve the global reservation and sanity check the hostname reserved.
+ host = hosts_cfg->get4(SUBNET_ID_GLOBAL, Host::IDENT_DUID, &duid[0], duid.size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ("global1", host->getHostname());
+
+ // Check that options are assigned correctly.
+ opt_dns = retrieveOption<Option4AddrLstPtr>(*host, DHO_NAME_SERVERS);
+ ASSERT_TRUE(opt_dns);
+ dns_addrs = opt_dns->getAddresses();
+ ASSERT_EQ(1, dns_addrs.size());
+ EXPECT_EQ("192.0.3.15", dns_addrs[0].toText());
+ opt_ttl = retrieveOption<OptionUint8Ptr>(*host, DHO_DEFAULT_IP_TTL);
+ ASSERT_TRUE(opt_ttl);
+ EXPECT_EQ(32, static_cast<int>(opt_ttl->getValue()));
+
+ // This reservation should be global solely and not assigned to
+ // either subnet
+ EXPECT_FALSE(hosts_cfg->get4(123, Host::IDENT_DUID, &duid[0], duid.size()));
+ EXPECT_FALSE(hosts_cfg->get4(542, Host::IDENT_DUID, &duid[0], duid.size()));
+}
+
+// Rather than disable these tests they are compiled out. This avoids them
+// reporting as disabled and thereby drawing attention to them.
+// This test verifies that configuration control with unsupported type fails
+TEST_F(Dhcp4ParserTest, configControlInfoNoFactory) {
+ string config = PARSER_CONFIGS[6];
+
+ // Unregister "mysql" and ignore the return value.
+ static_cast<void>(TestConfigBackendDHCPv4::
+ unregisterBackendType(ConfigBackendDHCPv4Mgr::instance(),
+ "mysql"));
+
+ // Should fail because "type=mysql" has no factories.
+ configure(config, CONTROL_RESULT_ERROR,
+ "during update from config backend database: "
+ "The type of the configuration backend: "
+ "'mysql' is not supported");
+}
+
+// This test verifies that configuration control info gets populated.
+TEST_F(Dhcp4ParserTest, configControlInfo) {
+ string config = PARSER_CONFIGS[6];
+
+ // Should be able to register a backend factory for "mysql".
+ ASSERT_TRUE(TestConfigBackendDHCPv4::
+ registerBackendType(ConfigBackendDHCPv4Mgr::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(Dhcp4ParserTest, serverTag) {
+ // Config without server-tag
+ string config_no_tag = "{ " + genIfaceConfig() + "," +
+ "\"subnet4\": [ ] "
+ "}";
+
+ // Config with server-tag
+ string config_tag = "{ " + genIfaceConfig() + "," +
+ "\"server-tag\": \"boo\", "
+ "\"subnet4\": [ ] "
+ "}";
+
+ // Config with an invalid server-tag
+ string bad_tag = "{ " + genIfaceConfig() + "," +
+ "\"server-tag\": 777, "
+ "\"subnet4\": [ ] "
+ "}";
+
+ // 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(parseDHCP4(bad_tag), std::exception);
+}
+
+// Check whether it is possible to configure packet queue
+TEST_F(Dhcp4ParserTest, dhcpQueueControl) {
+ struct Scenario {
+ std::string description_;
+ std::string json_;
+ };
+
+ std::vector<Scenario> scenarios = {
+ {
+ "no entry",
+ ""
+ },
+ {
+ "queue disabled",
+ "{ \n"
+ " \"enable-queue\": false \n"
+ "} \n"
+ },
+ {
+ "queue disabled, arbitrary content allowed",
+ "{ \n"
+ " \"enable-queue\": false, \n"
+ " \"foo\": \"bogus\", \n"
+ " \"random-int\" : 1234 \n"
+ "} \n"
+ },
+ {
+ "queue enabled, with queue-type",
+ "{ \n"
+ " \"enable-queue\": true, \n"
+ " \"queue-type\": \"some-type\" \n"
+ "} \n"
+ },
+ {
+ "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"
+ }
+ };
+
+ // 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_;
+ }
+
+ os << "} \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.
+ SimpleParser4::setDefaults(exp_control, SimpleParser4::DHCP_QUEUE_CONTROL4_DEFAULTS);
+
+ // Verify that the staged queue control equals the expected queue control.
+ EXPECT_TRUE(staged_control->equals(*exp_control));
+ }
+ }
+}
+
+// Check that we catch invalid dhcp-queue-control content
+TEST_F(Dhcp4ParserTest, 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 parseDHCP4() 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(parseDHCP4(os.str(), false)) << "parser returned empty element";
+ } catch(const std::exception& ex) {
+ error_msg = ex.what();
+ }
+
+ ASSERT_FALSE(error_msg.empty()) << "parseDHCP4 should have thrown";
+ EXPECT_EQ(scenario.exp_error_, error_msg);
+ }
+ }
+}
+
+// Checks inheritence of calculate-tee-times, t1-percent, t2-percent
+TEST_F(Dhcp4ParserTest, calculateTeeTimesInheritence) {
+ // Configure the server. This should succeed.
+ string config =
+ "{ \n"
+ " \"interfaces-config\": { \n"
+ " \"interfaces\": [\"*\" ] \n"
+ " }, \n"
+ " \"valid-lifetime\": 4000, \n"
+ " \"shared-networks\": [ { \n"
+ " \"name\": \"foo\", \n"
+ " \"calculate-tee-times\": true, \n"
+ " \"t1-percent\": .4, \n"
+ " \"t2-percent\": .75,\n"
+ " \"subnet4\": ["
+ " { "
+ " \"id\": 100,"
+ " \"subnet\": \"192.0.1.0/24\", \n"
+ " \"pools\": [ { \"pool\": \"192.0.1.1-192.0.1.10\" } ], \n"
+ " \"calculate-tee-times\": false,\n"
+ " \"t1-percent\": .45, \n"
+ " \"t2-percent\": .65 \n"
+ " }, \n"
+ " { \n"
+ " \"id\": 200, \n"
+ " \"subnet\": \"192.0.2.0/24\", \n"
+ " \"pools\": [ { \"pool\": \"192.0.2.1-192.0.2.10\"} ] \n"
+ " } \n"
+ " ] \n"
+ " } ], \n"
+ " \"subnet4\": [ { \n"
+ " \"id\": 300, \n"
+ " \"subnet\": \"192.0.3.0/24\", \n"
+ " \"pools\": [ { \"pool\": \"192.0.3.0 - 192.0.3.15\" } ]\n"
+ " } ] \n"
+ "} \n";
+
+ extractConfig(config);
+ configure(config, CONTROL_RESULT_SUCCESS, "");
+
+ CfgSubnets4Ptr subnets4 = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+
+ // Subnet 100 should use its own explicit values.
+ ConstSubnet4Ptr subnet4 = subnets4->getBySubnetId(100);
+ ASSERT_TRUE(subnet4);
+ EXPECT_FALSE(subnet4->getCalculateTeeTimes());
+ EXPECT_TRUE(util::areDoublesEquivalent(0.45, subnet4->getT1Percent()));
+ EXPECT_TRUE(util::areDoublesEquivalent(0.65, subnet4->getT2Percent()));
+
+ // Subnet 200 should use the shared-network values.
+ subnet4 = subnets4->getBySubnetId(200);
+ ASSERT_TRUE(subnet4);
+ EXPECT_EQ(true, subnet4->getCalculateTeeTimes());
+ EXPECT_TRUE(util::areDoublesEquivalent(0.4, subnet4->getT1Percent()));
+ EXPECT_TRUE(util::areDoublesEquivalent(0.75, subnet4->getT2Percent()));
+
+ // Subnet 300 should use the global values.
+ subnet4 = subnets4->getBySubnetId(300);
+ ASSERT_TRUE(subnet4);
+ EXPECT_FALSE(subnet4->getCalculateTeeTimes());
+ EXPECT_TRUE(util::areDoublesEquivalent(0.5, subnet4->getT1Percent()));
+ EXPECT_TRUE(util::areDoublesEquivalent(0.875, subnet4->getT2Percent()));
+}
+
+// This test checks that the global store-extended-info parameter is optional
+// and that values under the subnet are used.
+TEST_F(Dhcp4ParserTest, storeExtendedInfoNoGlobal) {
+ std::string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ "
+ "{"
+ " \"store-extended-info\": true,"
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ "},"
+ "{"
+ " \"store-extended-info\": false,"
+ " \"pools\": [ { \"pool\": \"192.0.3.1 - 192.0.3.100\" } ],"
+ " \"subnet\": \"192.0.3.0/24\""
+ "} ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ checkResult(status, 0);
+
+ CfgSubnets4Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+ Subnet4Ptr subnet1 = cfg->selectSubnet(IOAddress("192.0.2.1"));
+ ASSERT_TRUE(subnet1);
+ // Reset the fetch global function to staging (vs current) config.
+ subnet1->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getStagingCfg()->getConfiguredGlobals());
+ });
+ EXPECT_TRUE(subnet1->getStoreExtendedInfo());
+
+ Subnet4Ptr subnet2 = cfg->selectSubnet(IOAddress("192.0.3.1"));
+ ASSERT_TRUE(subnet2);
+ // Reset the fetch global function to staging (vs current) config.
+ subnet2->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getStagingCfg()->getConfiguredGlobals());
+ });
+ EXPECT_FALSE(subnet2->getStoreExtendedInfo());
+}
+
+// This test checks that the global store-extended-info parameter is used
+// when there is no such parameter under subnet and that the parameter
+// specified for a subnet overrides the global setting.
+TEST_F(Dhcp4ParserTest, storeExtendedInfoGlobal) {
+ std::string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"store-extended-info\": true,"
+ "\"subnet4\": [ "
+ "{"
+ " \"store-extended-info\": false,"
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ "},"
+ "{"
+ " \"pools\": [ { \"pool\": \"192.0.3.1 - 192.0.3.100\" } ],"
+ " \"subnet\": \"192.0.3.0/24\""
+ "} ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ checkResult(status, 0);
+
+ CfgSubnets4Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+ Subnet4Ptr subnet1 = cfg->selectSubnet(IOAddress("192.0.2.1"));
+ ASSERT_TRUE(subnet1);
+ // Reset the fetch global function to staging (vs current) config.
+ subnet1->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getStagingCfg()->getConfiguredGlobals());
+ });
+ EXPECT_FALSE(subnet1->getStoreExtendedInfo());
+
+ Subnet4Ptr subnet2 = cfg->selectSubnet(IOAddress("192.0.3.1"));
+ ASSERT_TRUE(subnet2);
+ // Reset the fetch global function to staging (vs current) config.
+ subnet2->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getStagingCfg()->getConfiguredGlobals());
+ });
+ EXPECT_TRUE(subnet2->getStoreExtendedInfo());
+}
+
+/// This test checks that the statistic-default-sample-count and age
+/// global parameters are committed to the stats manager as expected.
+TEST_F(Dhcp4ParserTest, statsDefaultLimits) {
+ std::string config = "{ " + genIfaceConfig() + "," +
+ "\"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 = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = configureDhcp4Server(*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(Dhcp4ParserTest, multiThreadingDefaultSettings) {
+ std::string config = "{ " + genIfaceConfig() + "," +
+ "\"subnet4\": [ ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ checkResult(status, 0);
+
+ ConstElementPtr cfg = CfgMgr::instance().getStagingCfg()->getDHCPMultiThreading();
+ ASSERT_TRUE(cfg);
+
+ std::string content_json =
+ "{"
+ " \"enable-multi-threading\": false,\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(Dhcp4ParserTest, multiThreadingSettings) {
+ std::string content_json =
+ "{"
+ " \"enable-multi-threading\": true,\n"
+ " \"thread-pool-size\": 48,\n"
+ " \"packet-queue-size\": 1024\n"
+ "}";
+ std::string config = "{ " + genIfaceConfig() + "," +
+ "\"subnet4\": [ ], "
+ "\"multi-threading\": " + content_json + "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = configureDhcp4Server(*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;
+}
+
+// Verify that parsing for the global parameter, parked-packet-limit,
+// is correct.
+TEST_F(Dhcp4ParserTest, parkedPacketLimit) {
+ // Config without parked-packet-limit
+ string config_no_limit = "{ " + genIfaceConfig() + "," +
+ "\"subnet4\": [ ] "
+ "}";
+
+ // Config with parked-packet-limit
+ string config_limit = "{ " + genIfaceConfig() + "," +
+ "\"parked-packet-limit\": 777, "
+ "\"subnet4\": [ ] "
+ "}";
+
+ // Config with an invalid parked-packet-limit
+ string bad_limit = "{ " + genIfaceConfig() + "," +
+ "\"parked-packet-limit\": \"boo\", "
+ "\"subnet4\": [ ] "
+ "}";
+
+ // Should not exist after construction.
+ ASSERT_FALSE(CfgMgr::instance().getStagingCfg()->getConfiguredGlobal("parked-packet-limit"));
+
+ // Configuration with no limit should default to 256.
+ configure(config_no_limit, CONTROL_RESULT_SUCCESS, "");
+ ConstElementPtr ppl;
+ ASSERT_TRUE(ppl = CfgMgr::instance().getStagingCfg()->getConfiguredGlobal("parked-packet-limit"));
+ EXPECT_EQ(256, ppl->intValue());
+
+ // Clear the config
+ CfgMgr::instance().clear();
+
+ // Configuration with the limit should have the limit value.
+ configure(config_limit, CONTROL_RESULT_SUCCESS, "");
+
+ ASSERT_TRUE(ppl = CfgMgr::instance().getStagingCfg()->getConfiguredGlobal("parked-packet-limit"));
+ EXPECT_EQ(777, ppl->intValue());
+
+ // Make sure an invalid limit fails to parse.
+ ASSERT_THROW(parseDHCP4(bad_limit), std::exception);
+}
+
+
+} // namespace
diff --git a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
new file mode 100644
index 0000000..589e442
--- /dev/null
+++ b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
@@ -0,0 +1,2092 @@
+// Copyright (C) 2012-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 <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <cc/command_interpreter.h>
+#include <config/command_mgr.h>
+#include <config/timeouts.h>
+#include <dhcp/dhcp4.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 <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/tests/dhcp4_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 <util/chrono_time_utils.h>
+#include <testutils/io_utils.h>
+#include <testutils/unix_control_client.h>
+#include <testutils/sandbox.h>
+
+#include "marker_file.h"
+#include "test_libraries.h"
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <cstdlib>
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+#include <thread>
+
+#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;
+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 NakedControlledDhcpv4Srv: public ControlledDhcpv4Srv {
+ // "Naked" DHCPv4 server, exposes internal fields
+public:
+ NakedControlledDhcpv4Srv():ControlledDhcpv4Srv(0) {
+ CfgMgr::instance().setFamily(AF_INET);
+ }
+
+ /// Expose internal methods for the sake of testing
+ using Dhcpv4Srv::receivePacket;
+ using Dhcpv4Srv::network_state_;
+};
+
+/// @brief Fixture class intended for testing control channel in the DHCPv4Srv
+class CtrlChannelDhcpv4SrvTest : public ::testing::Test {
+public:
+ isc::test::Sandbox sandbox;
+
+ /// @brief Path to the UNIX socket being used to communicate with the server
+ std::string socket_path_;
+
+ /// @brief Pointer to the tested server object
+ boost::shared_ptr<NakedControlledDhcpv4Srv> server_;
+
+ /// @brief Default constructor
+ ///
+ /// Sets socket path to its default value.
+ CtrlChannelDhcpv4SrvTest() {
+ const char* env = getenv("KEA_SOCKET_TEST_DIR");
+ if (env) {
+ socket_path_ = string(env) + "/kea4.sock";
+ } else {
+ socket_path_ = sandbox.join("kea4.sock");
+ }
+ reset();
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Destructor
+ ~CtrlChannelDhcpv4SrvTest() {
+ LeaseMgrFactory::destroy();
+ StatsMgr::instance().removeAll();
+
+ CommandMgr::instance().closeCommandSocket();
+ CommandMgr::instance().deregisterAll();
+ CommandMgr::instance().setConnectionTimeout(TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND);
+
+ server_.reset();
+ MultiThreadingMgr::instance().setMode(false);
+ };
+
+ /// @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\": [ \"*\" ]"
+ " },"
+ " \"expired-leases-processing\": {"
+ " \"reclaim-timer-wait-time\": 60,"
+ " \"hold-reclaimed-time\": 500,"
+ " \"flush-reclaimed-timer-wait-time\": 60"
+ " },"
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"subnet4\": [ ],"
+ " \"valid-lifetime\": 4000,"
+ " \"control-socket\": {"
+ " \"socket-type\": \"unix\","
+ " \"socket-name\": \"";
+
+ std::string footer =
+ "\" },"
+ " \"lease-database\": {"
+ " \"type\": \"memfile\", \"persist\": false },"
+ " \"loggers\": [ {"
+ " \"name\": \"kea-dhcp4\","
+ " \"severity\": \"INFO\","
+ " \"debuglevel\": 0"
+ " } ]"
+ "}";
+
+ // Fill in the socket-name value with socket_path_ to
+ // make the actual configuration text.
+ std::string config_txt = header + socket_path_ + footer;
+
+ ASSERT_NO_THROW(server_.reset(new NakedControlledDhcpv4Srv()));
+
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCP4(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 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));
+
+ IfaceMgr::instance().deleteAllExternalSockets();
+ CfgMgr::instance().clear();
+
+ // 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(0, "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(0, arguments));
+ }
+};
+
+TEST_F(CtrlChannelDhcpv4SrvTest, commands) {
+
+ ASSERT_NO_THROW(
+ server_.reset(new NakedControlledDhcpv4Srv());
+ );
+
+ // Use empty parameters list
+ ElementPtr params(new isc::data::MapElement());
+ int rcode = -1;
+
+ // Case 1: send bogus command
+ ConstElementPtr result = ControlledDhcpv4Srv::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 = ControlledDhcpv4Srv::processCommand("shutdown", params);
+ comment = parseAnswer(rcode, result);
+ EXPECT_EQ(0, rcode); // expect success
+ // Exit value should default to 0.
+ EXPECT_EQ(0, server_->getExitValue());
+
+ // Case 3: send shutdown command with exit-value parameter.
+ ConstElementPtr x(new isc::data::IntElement(77));
+ params->set("exit-value", x);
+
+ result = ControlledDhcpv4Srv::processCommand("shutdown", params);
+ comment = parseAnswer(rcode, result);
+ EXPECT_EQ(0, rcode); // expect success
+
+ // Exit value should match.
+ EXPECT_EQ(77, server_->getExitValue());
+}
+
+// Check that the "libreload" command will reload libraries
+TEST_F(CtrlChannelDhcpv4SrvTest, 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()));
+ 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));
+
+ // 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.\" }"
+ , 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, "21"));
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "1212"));
+}
+
+// Check that the "libreload" command will fail to reload libraries which are
+// not compatible when multi-threading is enabled
+TEST_F(CtrlChannelDhcpv4SrvTest, libreloadFailMultiThreading) {
+ 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()));
+ 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.\" }"
+ , 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"));
+}
+
+// This test checks which commands are registered by the DHCPv4 server.
+TEST_F(CtrlChannelDhcpv4SrvTest, 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.
+ ASSERT_NO_THROW(
+ server_.reset(new NakedControlledDhcpv4Srv());
+ );
+
+ 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-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.
+ server_.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(CtrlChannelDhcpv4SrvTest, 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(CtrlChannelDhcpv4SrvTest, controlChannelShutdown) {
+ createUnixChannelServer();
+ std::string response;
+
+ sendUnixCommand("{ \"command\": \"shutdown\" }", response);
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Shutting down.\" }",response);
+}
+
+// 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 Dhcpv4 statistics.
+TEST_F(CtrlChannelDhcpv4SrvTest, 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 = {
+ "pkt4-received",
+ "pkt4-discover-received",
+ "pkt4-offer-received",
+ "pkt4-request-received",
+ "pkt4-ack-received",
+ "pkt4-nak-received",
+ "pkt4-release-received",
+ "pkt4-decline-received",
+ "pkt4-inform-received",
+ "pkt4-unknown-received",
+ "pkt4-sent",
+ "pkt4-offer-sent",
+ "pkt4-ack-sent",
+ "pkt4-nak-sent",
+ "pkt4-parse-failed",
+ "pkt4-receive-drop",
+ "v4-allocation-fail",
+ "v4-allocation-fail-shared-network",
+ "v4-allocation-fail-subnet",
+ "v4-allocation-fail-no-pools",
+ "v4-allocation-fail-classes",
+ "v4-reservation-conflicts"
+ };
+
+ // preparing the schema which check if all statistics are set to zero
+ 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);
+}
+
+// Check that the "config-set" command will replace current configuration
+TEST_F(CtrlChannelDhcpv4SrvTest, 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 dhcp4_cfg_txt =
+ " \"Dhcp4\": { \n"
+ " \"interfaces-config\": { \n"
+ " \"interfaces\": [\"*\"] \n"
+ " }, \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"
+ " },"
+ " \"subnet4\": [ \n";
+ string subnet1 =
+ " {\"subnet\": \"192.2.0.0/24\", \n"
+ " \"pools\": [{ \"pool\": \"192.2.0.1-192.2.0.50\" }]}\n";
+ string subnet2 =
+ " {\"subnet\": \"192.2.1.0/24\", \n"
+ " \"pools\": [{ \"pool\": \"192.2.1.1-192.2.1.50\" }]}\n";
+ string bad_subnet =
+ " {\"comment\": \"192.2.2.0/24\", \n"
+ " \"pools\": [{ \"pool\": \"192.2.2.1-192.2.2.50\" }]}\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\": \"dhcp4\",\n"
+ " \"encapsulate\": \"\"\n"
+ " }\n"
+ "]\n";
+ string option_data =
+ " ,\"option-data\": [\n"
+ " {\n"
+ " \"name\": \"foo\",\n"
+ " \"code\": 163,\n"
+ " \"space\": \"dhcp4\",\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
+ << dhcp4_cfg_txt
+ << subnet1
+ << subnet_footer
+ << option_def
+ << option_data
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << logger_txt
+ << "}\n" // close dhcp4
+ << "}}";
+
+ // Send the config-set command
+ std::string response;
+ sendUnixCommand(os.str(), response);
+
+ // Verify the configuration was successful.
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration successful.\" }",
+ response);
+
+ // Check that the config was indeed applied.
+ const Subnet4Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+ EXPECT_EQ(1, subnets->size());
+
+ OptionDefinitionPtr def =
+ LibDHCP::getRuntimeOptionDef(DHCP4_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
+ << dhcp4_cfg_txt
+ << bad_subnet
+ << subnet_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << "}\n" // close dhcp4
+ << "}}";
+
+ // 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>:19:17)\" }",
+ response);
+
+ // Check that the config was not lost
+ subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+ EXPECT_EQ(1, subnets->size());
+
+ def = LibDHCP::getRuntimeOptionDef(DHCP4_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
+ << dhcp4_cfg_txt
+ << subnet1
+ << ",\n"
+ << subnet2
+ << subnet_footer
+ << "}\n" // close dhcp4
+ << "}}";
+
+ // 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_));
+
+ // With no command channel, should still receive the response.
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration successful.\" }",
+ response);
+
+ // Check that the config was not lost
+ subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->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(CtrlChannelDhcpv4SrvTest, 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("Dhcp4"));
+ EXPECT_TRUE(cfg->get("Dhcp4")->get("loggers"));
+}
+
+// Verify that the "config-test" command will do what we expect.
+TEST_F(CtrlChannelDhcpv4SrvTest, 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 dhcp4_cfg_txt =
+ " \"Dhcp4\": { \n"
+ " \"interfaces-config\": { \n"
+ " \"interfaces\": [\"*\"] \n"
+ " }, \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"
+ " },"
+ " \"subnet4\": [ \n";
+ string subnet1 =
+ " {\"subnet\": \"192.2.0.0/24\", \n"
+ " \"pools\": [{ \"pool\": \"192.2.0.1-192.2.0.50\" }]}\n";
+ string subnet2 =
+ " {\"subnet\": \"192.2.1.0/24\", \n"
+ " \"pools\": [{ \"pool\": \"192.2.1.1-192.2.1.50\" }]}\n";
+ string bad_subnet =
+ " {\"comment\": \"192.2.2.0/24\", \n"
+ " \"pools\": [{ \"pool\": \"192.2.2.1-192.2.2.50\" }]}\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
+ << dhcp4_cfg_txt
+ << subnet1
+ << subnet_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << logger_txt
+ << "}\n" // close dhcp4
+ << "}}";
+
+ // Send the config-set command
+ std::string response;
+ sendUnixCommand(os.str(), response);
+
+ // Verify the configuration was successful.
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration successful.\" }",
+ response);
+
+ // Check that the config was indeed applied.
+ const Subnet4Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->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
+ << dhcp4_cfg_txt
+ << bad_subnet
+ << subnet_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << "}\n" // close dhcp4
+ << "}}";
+
+ // 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>:19:17)\" }",
+ response);
+
+ // Check that the config was not lost
+ subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->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
+ << dhcp4_cfg_txt
+ << subnet1
+ << ",\n"
+ << subnet2
+ << subnet_footer
+ << "}\n" // close dhcp4
+ << "}}";
+
+ // 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()->getCfgSubnets4()->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(CtrlChannelDhcpv4SrvTest, 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 server-tag-get command
+TEST_F(CtrlChannelDhcpv4SrvTest, 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 status-get commands
+TEST_F(CtrlChannelDhcpv4SrvTest, 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_FALSE(found_multi_threading->boolValue());
+
+ auto found_thread_count = arguments->get("thread-pool-size");
+ ASSERT_FALSE(found_thread_count);
+
+ auto found_queue_size = arguments->get("packet-queue-size");
+ ASSERT_FALSE(found_queue_size);
+
+ auto found_queue_stats = arguments->get("packet-queue-statistics");
+ ASSERT_FALSE(found_queue_stats);
+
+ 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(CtrlChannelDhcpv4SrvTest, 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(CtrlChannelDhcpv4SrvTest, statusGetSocketsErrors) {
+ // Create dummy interfaces to test socket status and add custom down and no-address interfaces.
+ 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));
+ test_config.addIface("no_address", 5);
+
+ // 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(2, 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());
+
+ error = errors->get(1);
+ ASSERT_TRUE(error);
+ ASSERT_EQ(Element::string, error->getType());
+ ASSERT_EQ("the interface no_address has no usable IPv4 addresses configured",
+ error->stringValue());
+}
+
+// This test verifies that the DHCP server handles config-backend-pull command
+TEST_F(CtrlChannelDhcpv4SrvTest, 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(CtrlChannelDhcpv4SrvTest, controlLeasesReclaim) {
+ createUnixChannelServer();
+
+ // Create expired leases. Leases are expired by 40 seconds ago
+ // (valid lifetime = 60, cltt = now - 100).
+ HWAddrPtr hwaddr0(new HWAddr(HWAddr::fromText("00:01:02:03:04:05")));
+ Lease4Ptr lease0(new Lease4(IOAddress("10.0.0.1"), hwaddr0,
+ ClientIdPtr(), 60,
+ time(NULL) - 100, SubnetID(1)));
+ HWAddrPtr hwaddr1(new HWAddr(HWAddr::fromText("01:02:03:04:05:06")));
+ Lease4Ptr lease1(new Lease4(IOAddress("10.0.0.2"), hwaddr1,
+ ClientIdPtr(), 60,
+ time(NULL) - 100, SubnetID(1)));
+
+ // 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.getLease4(IOAddress("10.0.0.1")));
+ ASSERT_TRUE(lease_mgr.getLease4(IOAddress("10.0.0.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.getLease4(IOAddress("10.0.0.1")));
+ ASSERT_NO_THROW(lease1 = lease_mgr.getLease4(IOAddress("10.0.0.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(CtrlChannelDhcpv4SrvTest, controlLeasesReclaimRemove) {
+ createUnixChannelServer();
+
+ // Create expired leases. Leases are expired by 40 seconds ago
+ // (valid lifetime = 60, cltt = now - 100).
+ HWAddrPtr hwaddr0(new HWAddr(HWAddr::fromText("00:01:02:03:04:05")));
+ Lease4Ptr lease0(new Lease4(IOAddress("10.0.0.1"), hwaddr0,
+ ClientIdPtr(), 60,
+ time(NULL) - 100, SubnetID(1)));
+ HWAddrPtr hwaddr1(new HWAddr(HWAddr::fromText("01:02:03:04:05:06")));
+ Lease4Ptr lease1(new Lease4(IOAddress("10.0.0.2"), hwaddr1,
+ ClientIdPtr(), 60,
+ time(NULL) - 100, SubnetID(1)));
+
+ // 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.getLease4(IOAddress("10.0.0.1")));
+ ASSERT_TRUE(lease_mgr.getLease4(IOAddress("10.0.0.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.getLease4(IOAddress("10.0.0.1")));
+ ASSERT_NO_THROW(lease1 = lease_mgr.getLease4(IOAddress("10.0.0.2")));
+ ASSERT_FALSE(lease0);
+ ASSERT_FALSE(lease1);
+}
+
+// Tests that the server properly responds to shutdown command sent
+// via ControlChannel
+TEST_F(CtrlChannelDhcpv4SrvTest, 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-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(CtrlChannelDhcpv4SrvTest, 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(CtrlChannelDhcpv4SrvTest, 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(CtrlChannelDhcpv4SrvTest, 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(CtrlChannelDhcpv4SrvTest, 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(CtrlChannelDhcpv4SrvTest, 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 =
+ "{ \"Dhcp4\": {"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"subnet4\": ["
+ " { \"subnet\": \"192.0.2.0/24\" },"
+ " { \"subnet\": \"192.0.3.0/24\" }"
+ " ],"
+ " \"valid-lifetime\": 4000,"
+ " \"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.
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration successful.\" }",
+ response);
+
+ // Check that the config was indeed applied.
+ const Subnet4Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+ EXPECT_EQ(2, subnets->size());
+
+ ::remove("test8.json");
+}
+
+// This test verifies that disable DHCP service command performs sanity check on
+// parameters.
+TEST_F(CtrlChannelDhcpv4SrvTest, 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(CtrlChannelDhcpv4SrvTest, 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(CtrlChannelDhcpv4SrvTest, 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(CtrlChannelDhcpv4SrvTest, 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(CtrlChannelDhcpv4SrvTest, 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(CtrlChannelDhcpv4SrvTest, 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(CtrlChannelDhcpv4SrvTest, 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(&CtrlChannelDhcpv4SrvTest::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(CtrlChannelDhcpv4SrvTest, 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(&CtrlChannelDhcpv4SrvTest::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(CtrlChannelDhcpv4SrvTest, 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(CtrlChannelDhcpv4SrvTest, 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/dhcp4/tests/d2_unittest.cc b/src/bin/dhcp4/tests/d2_unittest.cc
new file mode 100644
index 0000000..0a11144
--- /dev/null
+++ b/src/bin/dhcp4/tests/d2_unittest.cc
@@ -0,0 +1,525 @@
+// 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 <dhcp/iface_mgr.h>
+#include <dhcp4/json_config_parser.h>
+#include <dhcp4/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 {
+
+void
+D2Dhcpv4Srv::d2ClientErrorHandler(const
+ dhcp_ddns::NameChangeSender::Result result,
+ dhcp_ddns::NameChangeRequestPtr& ncr) {
+ ++error_count_;
+ // call base class error handler
+ Dhcpv4Srv::d2ClientErrorHandler(result, ncr);
+}
+
+const bool Dhcp4SrvD2Test::SHOULD_PASS;
+const bool Dhcp4SrvD2Test::SHOULD_FAIL;
+
+Dhcp4SrvD2Test::Dhcp4SrvD2Test() : rcode_(-1) {
+}
+
+Dhcp4SrvD2Test::~Dhcp4SrvD2Test() {
+ reset();
+}
+
+dhcp_ddns::NameChangeRequestPtr
+Dhcp4SrvD2Test::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
+Dhcp4SrvD2Test::reset() {
+ CfgMgr::instance().clear();
+
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"hooks-libraries\": [ ], "
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"subnet4\": [ ], "
+ "\"dhcp-ddns\": { \"enable-updates\" : false }, "
+ "\"option-def\": [ ], "
+ "\"option-data\": [ ] }";
+ configure(config, SHOULD_PASS);
+}
+
+void
+Dhcp4SrvD2Test::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\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ " \"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
+Dhcp4SrvD2Test::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 = configureDhcp4Server(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 Dhcp4 server
+// and then invoking its startD2() method.
+TEST_F(Dhcp4SrvD2Test, 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 Dhcp4 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(Dhcp4SrvD2Test, 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(Dhcp4SrvD2Test, 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(Dhcp4SrvD2Test, 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, "127.0.0.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(Dhcp4SrvD2Test, 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 Dhcp4Srv'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 OSes
+// except Solaris 11.
+/// @todo Eventually we should find a way to test this under Solaris.
+#ifndef OS_SOLARIS
+TEST_F(Dhcp4SrvD2Test, forceUDPSendFailure) {
+#else
+TEST_F(Dhcp4SrvD2Test, 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.0.0.0", 0,
+ "0.0.0.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(Dhcp4SrvD2Test, 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());
+}
+
+// Tests invalid config with TCP protocol
+TEST_F(Dhcp4SrvD2Test, badTCP) {
+ std::string config =
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ " \"dhcp-ddns\" : {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"127.0.0.1\", "
+ " \"server-port\" : 53001, "
+ " \"sender-ip\" : \"0.0.0.0\", "
+ " \"sender-port\" : 0, "
+ " \"max-queue-size\" : 1024, "
+ " \"ncr-protocol\" : \"TCP\", "
+ " \"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 }";
+
+ ElementPtr json = Element::fromJSON(config);
+ ConstElementPtr status;
+
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv_, json));
+ ASSERT_TRUE(status);
+ int rcode;
+ ConstElementPtr comment = config::parseAnswer(rcode, status);
+ EXPECT_EQ(1, rcode);
+}
+
+// Tests invalid config with bad sender family
+TEST_F(Dhcp4SrvD2Test, badFamily) {
+ std::string config =
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ " \"dhcp-ddns\" : {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"127.0.0.1\", "
+ " \"server-port\" : 53001, "
+ " \"sender-ip\" : \"::\", "
+ " \"sender-port\" : 0, "
+ " \"max-queue-size\" : 1024, "
+ " \"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 }";
+
+ ElementPtr json = Element::fromJSON(config);
+ ConstElementPtr status;
+
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv_, json));
+ ASSERT_TRUE(status);
+ int rcode;
+ ConstElementPtr comment = config::parseAnswer(rcode, status);
+ EXPECT_EQ(1, rcode);
+}
+
+// Tests invalid config with server == sender
+TEST_F(Dhcp4SrvD2Test, senderEqServer) {
+ std::string config =
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ " \"dhcp-ddns\" : {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"127.0.0.1\", "
+ " \"server-port\" : 53001, "
+ " \"sender-ip\" : \"127.0.0.1\", "
+ " \"sender-port\" : 53001, "
+ " \"max-queue-size\" : 1024, "
+ " \"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 }";
+
+ ElementPtr json = Element::fromJSON(config);
+ ConstElementPtr status;
+
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv_, json));
+ ASSERT_TRUE(status);
+ int rcode;
+ ConstElementPtr comment = config::parseAnswer(rcode, status);
+ EXPECT_EQ(1, rcode);
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
diff --git a/src/bin/dhcp4/tests/d2_unittest.h b/src/bin/dhcp4/tests/d2_unittest.h
new file mode 100644
index 0000000..20474e3
--- /dev/null
+++ b/src/bin/dhcp4/tests/d2_unittest.h
@@ -0,0 +1,115 @@
+// 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 Dhcpv4srv with D2ClientMgr
+
+#ifndef D2_UNITTEST_H
+#define D2_UNITTEST_H
+
+#include <dhcp4/dhcp4_srv.h>
+#include <cc/command_interpreter.h>
+
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test derivation of Dhcpv4Srv 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 D2Dhcpv4Srv : public Dhcpv4Srv {
+public:
+ /// @brief Counts the number of times the client error handler is called.
+ int error_count_;
+
+ /// @brief Constructor
+ D2Dhcpv4Srv()
+ : Dhcpv4Srv(0, false, false), error_count_(0) {
+ }
+
+ /// @brief virtual Destructor.
+ virtual ~D2Dhcpv4Srv() {
+ }
+
+ /// @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 Dhcpv4Srv.
+class Dhcp4SrvD2Test : 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
+ Dhcp4SrvD2Test();
+
+ /// @brief virtual Destructor
+ virtual ~Dhcp4SrvD2Test();
+
+ /// @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 exp_result indicates if configuration should pass or fail
+ /// @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 = "127.0.0.1",
+ const size_t port = 53001,
+ const std::string& sender_ip = "0.0.0.0",
+ 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.
+ D2Dhcpv4Srv 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/dhcp4/tests/decline_unittest.cc b/src/bin/dhcp4/tests/decline_unittest.cc
new file mode 100644
index 0000000..b694bab
--- /dev/null
+++ b/src/bin/dhcp4/tests/decline_unittest.cc
@@ -0,0 +1,292 @@
+// 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 <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp4/tests/dhcp4_client.h>
+#include <stats/stats_mgr.h>
+#include <boost/shared_ptr.hpp>
+#include <sstream>
+
+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 Decline tests.
+///
+/// - Configuration 0:
+/// - Used for testing Decline message processing
+/// - 1 subnet: 10.0.0.0/24
+/// - 1 pool: 10.0.0.10-10.0.0.100
+/// - Router option present: 10.0.0.200 and 10.0.0.201
+const char* DECLINE_CONFIGS[] = {
+// Configuration 0
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"routers\","
+ " \"data\": \"10.0.0.200,10.0.0.201\""
+ " } ]"
+ " } ]"
+ "}"
+};
+
+};
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+void
+Dhcpv4SrvTest::acquireLease(Dhcp4Client& client) {
+ // Perform 4-way exchange with the server but to not request any
+ // specific address in the DHCPDISCOVER message.
+ ASSERT_NO_THROW(client.doDORA());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Response must not be relayed.
+ EXPECT_FALSE(resp->isRelayed());
+ // Make sure that the server id is present.
+ EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+ // Make sure that the client has got the lease with the requested address.
+ ASSERT_NE(client.config_.lease_.addr_.toText(), "0.0.0.0");
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(client.config_.lease_.addr_);
+ ASSERT_TRUE(lease);
+}
+
+void
+Dhcpv4SrvTest::acquireAndDecline(Dhcp4Client& client,
+ const std::string& hw_address_1,
+ const std::string& client_id_1,
+ const std::string& hw_address_2,
+ const std::string& client_id_2,
+ ExpectedResult expected_result) {
+
+ // Set this global statistic explicitly to zero.
+ isc::stats::StatsMgr::instance().setValue("declined-addresses",
+ static_cast<int64_t>(0));
+
+ // Ok, do the normal lease acquisition.
+ CfgMgr::instance().clear();
+
+ // Configure DHCP server.
+ configure(DECLINE_CONFIGS[0], *client.getServer());
+ // Explicitly set the client id.
+ client.includeClientId(client_id_1);
+ // Explicitly set the HW address.
+ client.setHWAddress(hw_address_1);
+ // Perform 4-way exchange to obtain a new lease.
+ acquireLease(client);
+
+ // Let's get the subnet-id and generate statistics name out of it.
+ const Subnet4Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+ ASSERT_EQ(1, subnets->size());
+ std::stringstream name;
+ name << "subnet[" << (*subnets->begin())->getID() << "].declined-addresses";
+
+ // Set the subnet specific statistic explicitly to zero.
+ isc::stats::StatsMgr::instance().setValue(name.str(), static_cast<int64_t>(0));
+
+ // Check the declined-addresses (subnet) statistic before the Decline operation.
+ ObservationPtr declined_cnt = StatsMgr::instance().getObservation(name.str());
+ 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;
+
+ // Remember the acquired address.
+ IOAddress declined_address = client.config_.lease_.addr_;
+
+ // Explicitly set the client id for DHCPDECLINE.
+ client.includeClientId(client_id_2);
+ // Explicitly set the HW address for DHCPDECLINE.
+ client.setHWAddress(hw_address_2);
+
+ // Send the decline and make sure that the lease is removed from the
+ // server.
+ ASSERT_NO_THROW(client.doDecline());
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(declined_address);
+
+ declined_cnt = StatsMgr::instance().getObservation(name.str());
+ 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;
+
+ ASSERT_TRUE(lease);
+ // 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) {
+ EXPECT_EQ(Lease::STATE_DECLINED, lease->state_);
+
+ // The decline succeeded, so the declined-addresses statistic should
+ // be increased by one
+ EXPECT_EQ(after, before + 1);
+
+ EXPECT_EQ(after_global, before_global + 1);
+ } else {
+ // the decline was supposed, to be rejected.
+ EXPECT_EQ(Lease::STATE_DEFAULT, lease->state_);
+
+ // The decline failed, so the declined-addresses should be the same
+ // as before
+ EXPECT_EQ(before, after);
+ EXPECT_EQ(before_global, after_global);
+ }
+}
+
+}; // end of isc::dhcp::test namespace
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+namespace {
+
+/// @brief Test fixture class for testing DHCPDECLINE message handling.
+///
+/// @todo This class is very similar to ReleaseTest. Maybe we could
+/// merge those two classes one day and use derived classes?
+class DeclineTest : public Dhcpv4SrvTest {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets up fake interfaces.
+ DeclineTest()
+ : Dhcpv4SrvTest(),
+ iface_mgr_test_config_(true) {
+ }
+
+ /// @brief Interface Manager's fake configuration control.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+
+};
+
+// This test checks that the client can acquire and decline the lease.
+TEST_F(DeclineTest, declineNoIdentifierChange) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ acquireAndDecline(client, "01:02:03:04:05:06", "12:14",
+ "01:02:03:04:05:06", "12:14",
+ SHOULD_PASS);
+}
+
+// This test verifies the decline correctness in the following case:
+// - Client acquires new lease using HW address only
+// - Client sends the DHCPDECLINE with valid HW address and without
+// client identifier.
+// - The server successfully declines the lease.
+TEST_F(DeclineTest, declineHWAddressOnly) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ acquireAndDecline(client, "01:02:03:04:05:06", "",
+ "01:02:03:04:05:06", "",
+ SHOULD_PASS);
+}
+
+// This test verifies the decline correctness in the following case:
+// - Client acquires new lease using the client identifier and HW address
+// - Client sends the DHCPDECLINE with valid HW address but with no
+// client identifier.
+// - The server successfully declines the lease.
+TEST_F(DeclineTest, declineNoClientId) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ acquireAndDecline(client, "01:02:03:04:05:06", "12:14",
+ "01:02:03:04:05:06", "",
+ SHOULD_PASS);
+}
+
+// This test verifies the decline correctness in the following case:
+// - Client acquires new lease using HW address
+// - Client sends the DHCPDECLINE with valid HW address and some
+// client identifier.
+// - The server identifies the lease using HW address and declines
+// this lease.
+TEST_F(DeclineTest, declineNoClientId2) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ acquireAndDecline(client, "01:02:03:04:05:06", "",
+ "01:02:03:04:05:06", "12:14",
+ SHOULD_PASS);
+}
+
+// This test checks the server's behavior in the following case:
+// - Client acquires new lease using the client identifier and HW address
+// - Client sends the DHCPDECLINE with the valid HW address but with invalid
+// client identifier.
+// - The server should not remove the lease.
+TEST_F(DeclineTest, declineNonMatchingClientId) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ acquireAndDecline(client, "01:02:03:04:05:06", "12:14",
+ "01:02:03:04:05:06", "12:15:16",
+ SHOULD_FAIL);
+}
+
+// This test checks the server's behavior in the following case:
+// - Client acquires new lease using client identifier and HW address
+// - Client sends the DHCPDECLINE with the same client identifier but
+// different HW address.
+// - The server uses client identifier to find the client's lease and
+// declines it.
+TEST_F(DeclineTest, declineNonMatchingHWAddress) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ acquireAndDecline(client, "01:02:03:04:05:06", "12:14",
+ "06:06:06:06:06:06", "12:14",
+ SHOULD_PASS);
+}
+
+// This test checks the server's behavior in the following case:
+// - Client acquires new lease (address A).
+// - Client sends DHCPDECLINE with the requested IP address set to a different
+// address B than it has acquired from the server.
+// - Server determines that the client is trying to decline a
+// wrong address and will refuse to decline.
+TEST_F(DeclineTest, declineNonMatchingIPAddress) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Configure DHCP server.
+ configure(DECLINE_CONFIGS[0], *client.getServer());
+ // Perform 4-way exchange to obtain a new lease.
+ acquireLease(client);
+
+ // Remember the acquired address.
+ IOAddress leased_address = client.config_.lease_.addr_;
+
+ // Modify the client's address to force it to decline a different address
+ // than it has obtained from the server.
+ client.config_.lease_.addr_ = IOAddress(leased_address.toUint32() + 1);
+
+ // Send DHCPDECLINE and make sure it was unsuccessful, i.e. the lease
+ // remains in the database.
+ ASSERT_NO_THROW(client.doDecline());
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(leased_address);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(Lease::STATE_DEFAULT, lease->state_);
+}
+
+}; // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/dhcp4_client.cc b/src/bin/dhcp4/tests/dhcp4_client.cc
new file mode 100644
index 0000000..b054a0d
--- /dev/null
+++ b/src/bin/dhcp4/tests/dhcp4_client.cc
@@ -0,0 +1,600 @@
+// Copyright (C) 2014-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 <dhcp/dhcp4.h>
+#include <dhcp/option.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/lease.h>
+#include <dhcp4/tests/dhcp4_client.h>
+#include <util/multi_threading_mgr.h>
+#include <util/range_utilities.h>
+#include <boost/pointer_cast.hpp>
+#include <cstdlib>
+
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+Dhcp4Client::Configuration::Configuration()
+ : routers_(), dns_servers_(), log_servers_(), quotes_servers_(),
+ serverid_("0.0.0.0"), siaddr_(IOAddress::IPV4_ZERO_ADDRESS()) {
+ reset();
+}
+
+void
+Dhcp4Client::Configuration::reset() {
+ routers_.clear();
+ dns_servers_.clear();
+ log_servers_.clear();
+ quotes_servers_.clear();
+ serverid_ = IOAddress("0.0.0.0");
+ siaddr_ = IOAddress::IPV4_ZERO_ADDRESS();
+ sname_.clear();
+ boot_file_name_.clear();
+ lease_ = Lease4();
+}
+
+Dhcp4Client::Dhcp4Client(const Dhcp4Client::State& state) :
+ config_(),
+ ciaddr_(),
+ curr_transid_(0),
+ dest_addr_("255.255.255.255"),
+ hwaddr_(generateHWAddr()),
+ clientid_(),
+ iface_name_("eth0"),
+ iface_index_(ETH0_INDEX),
+ relay_addr_("192.0.2.2"),
+ requested_options_(),
+ server_facing_relay_addr_("10.0.0.2"),
+ srv_(boost::shared_ptr<NakedDhcpv4Srv>(new NakedDhcpv4Srv(0))),
+ state_(state),
+ use_relay_(false),
+ circuit_id_() {
+}
+
+Dhcp4Client::Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv> srv,
+ const Dhcp4Client::State& state) :
+ config_(),
+ ciaddr_(),
+ curr_transid_(0),
+ dest_addr_("255.255.255.255"),
+ fqdn_(),
+ hwaddr_(generateHWAddr()),
+ clientid_(),
+ iface_name_("eth0"),
+ iface_index_(ETH0_INDEX),
+ relay_addr_("192.0.2.2"),
+ requested_options_(),
+ server_facing_relay_addr_("10.0.0.2"),
+ srv_(srv),
+ state_(state),
+ use_relay_(false),
+ circuit_id_() {
+}
+
+void
+Dhcp4Client::addRequestedAddress(const IOAddress& addr) {
+ if (context_.query_) {
+ Option4AddrLstPtr opt(new Option4AddrLst(DHO_DHCP_REQUESTED_ADDRESS,
+ addr));
+ context_.query_->addOption(opt);
+ }
+}
+
+void
+Dhcp4Client::appendClientId() {
+ if (!context_.query_) {
+ isc_throw(Dhcp4ClientError, "pointer to the query must not be NULL"
+ " when adding Client Identifier option");
+ }
+
+ if (clientid_) {
+ OptionPtr opt(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER,
+ clientid_->getClientId()));
+ context_.query_->addOption(opt);
+ }
+}
+
+void
+Dhcp4Client::appendServerId() {
+ OptionPtr opt(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
+ config_.serverid_));
+ context_.query_->addOption(opt);
+}
+
+void
+Dhcp4Client::appendName() {
+ if (!context_.query_) {
+ isc_throw(Dhcp4ClientError, "pointer to the query must not be NULL"
+ " when adding FQDN or Hostname option");
+ }
+
+ if (fqdn_) {
+ context_.query_->addOption(fqdn_);
+
+ } else if (hostname_) {
+ context_.query_->addOption(hostname_);
+ }
+}
+
+void
+Dhcp4Client::appendPRL() {
+ if (!context_.query_) {
+ isc_throw(Dhcp4ClientError, "pointer to the query must not be NULL"
+ " when adding option codes to the PRL option");
+
+ } else if (!requested_options_.empty()) {
+ // Include Parameter Request List if at least one option code
+ // has been specified to be requested.
+ OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+ DHO_DHCP_PARAMETER_REQUEST_LIST));
+ for (std::set<uint8_t>::const_iterator opt = requested_options_.begin();
+ opt != requested_options_.end(); ++opt) {
+ prl->addValue(*opt);
+ }
+ context_.query_->addOption(prl);
+ }
+}
+
+void
+Dhcp4Client::applyConfiguration() {
+ Pkt4Ptr resp = context_.response_;
+ if (!resp) {
+ return;
+ }
+
+ // Let's keep the old lease in case this is a response to Inform.
+ Lease4 old_lease = config_.lease_;
+ config_.reset();
+
+ // Routers
+ Option4AddrLstPtr opt_routers = boost::dynamic_pointer_cast<
+ Option4AddrLst>(resp->getOption(DHO_ROUTERS));
+ if (opt_routers) {
+ config_.routers_ = opt_routers->getAddresses();
+ }
+ // DNS Servers
+ Option4AddrLstPtr opt_dns_servers = boost::dynamic_pointer_cast<
+ Option4AddrLst>(resp->getOption(DHO_DOMAIN_NAME_SERVERS));
+ if (opt_dns_servers) {
+ config_.dns_servers_ = opt_dns_servers->getAddresses();
+ }
+ // Log Servers
+ Option4AddrLstPtr opt_log_servers = boost::dynamic_pointer_cast<
+ Option4AddrLst>(resp->getOption(DHO_LOG_SERVERS));
+ if (opt_log_servers) {
+ config_.log_servers_ = opt_log_servers->getAddresses();
+ }
+ // Quotes Servers
+ Option4AddrLstPtr opt_quotes_servers = boost::dynamic_pointer_cast<
+ Option4AddrLst>(resp->getOption(DHO_COOKIE_SERVERS));
+ if (opt_quotes_servers) {
+ config_.quotes_servers_ = opt_quotes_servers->getAddresses();
+ }
+ // Vendor Specific options
+ OptionVendorPtr opt_vendor = boost::dynamic_pointer_cast<
+ OptionVendor>(resp->getOption(DHO_VIVSO_SUBOPTIONS));
+ if (opt_vendor) {
+ config_.vendor_suboptions_ = opt_vendor->getOptions();
+ }
+ // siaddr
+ config_.siaddr_ = resp->getSiaddr();
+ // sname
+ OptionBuffer buf = resp->getSname();
+ // sname is a fixed length field holding null terminated string. Use
+ // of c_str() guarantees that only a useful portion (ending with null
+ // character) is assigned.
+ config_.sname_.assign(std::string(buf.begin(), buf.end()).c_str());
+ // (boot)file
+ buf = resp->getFile();
+ config_.boot_file_name_.assign(std::string(buf.begin(), buf.end()).c_str());
+ // Server Identifier
+ OptionCustomPtr opt_serverid = boost::dynamic_pointer_cast<
+ OptionCustom>(resp->getOption(DHO_DHCP_SERVER_IDENTIFIER));
+ if (opt_serverid) {
+ config_.serverid_ = opt_serverid->readAddress();
+ }
+
+ // If the message sent was Inform, we don't want to throw
+ // away the old lease info, just the bits about options.
+ if (context_.query_->getType() == DHCPINFORM) {
+ config_.lease_ = old_lease;
+ } else {
+ /// @todo Set the valid lifetime, t1, t2 etc.
+ config_.lease_ = Lease4(IOAddress(context_.response_->getYiaddr()),
+ context_.response_->getHWAddr(),
+ 0, 0, 0, time(NULL), 0, false, false,
+ "");
+ }
+}
+
+void
+Dhcp4Client::createLease(const IOAddress& addr, const uint32_t valid_lft) {
+ Lease4 lease(addr, hwaddr_, 0, 0, valid_lft,
+ time(NULL), 0, false, false, "");
+ config_.lease_ = lease;
+}
+
+Pkt4Ptr
+Dhcp4Client::createMsg(const uint8_t msg_type) {
+ Pkt4Ptr msg(new Pkt4(msg_type, curr_transid_++));
+ msg->setHWAddr(hwaddr_);
+ return (msg);
+}
+
+void
+Dhcp4Client::appendExtraOptions() {
+ // 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) {
+ // Call base class function so that unittests can add multiple
+ // options with the same code.
+ context_.query_->Pkt::addOption(opt->second);
+ }
+ }
+}
+
+void
+Dhcp4Client::appendClasses() {
+ for (ClientClasses::const_iterator cclass = classes_.cbegin();
+ cclass != classes_.cend(); ++cclass) {
+ context_.query_->addClass(*cclass);
+ }
+}
+
+void
+Dhcp4Client::doDiscover(const boost::shared_ptr<IOAddress>& requested_addr) {
+ context_.query_ = createMsg(DHCPDISCOVER);
+ // Request options if any.
+ appendPRL();
+ // Include FQDN or Hostname.
+ appendName();
+ // Include Client Identifier
+ appendClientId();
+ if (requested_addr) {
+ addRequestedAddress(*requested_addr);
+ }
+ // Override the default ciaddr if specified by a test.
+ if (!ciaddr_.unspecified()) {
+ context_.query_->setCiaddr(ciaddr_);
+ }
+ appendExtraOptions();
+ appendClasses();
+
+ // Send the message to the server.
+ sendMsg(context_.query_);
+ // Expect response.
+ context_.response_ = receiveOneMsg();
+}
+
+void
+Dhcp4Client::doDORA(const boost::shared_ptr<IOAddress>& requested_addr) {
+ doDiscover(requested_addr);
+ if (context_.response_ && (context_.response_->getType() == DHCPOFFER)) {
+ doRequest();
+ }
+}
+
+void
+Dhcp4Client::doInform(const bool set_ciaddr) {
+ context_.query_ = createMsg(DHCPINFORM);
+ // Request options if any.
+ appendPRL();
+ // Any other options to be sent by a client.
+ appendExtraOptions();
+ // Add classes.
+ appendClasses();
+ // The client sending a DHCPINFORM message has an IP address obtained
+ // by some other means, e.g. static configuration. The lease which we
+ // are using here is most likely set by the createLease method.
+ if (set_ciaddr) {
+ context_.query_->setCiaddr(config_.lease_.addr_);
+ }
+ context_.query_->setLocalAddr(config_.lease_.addr_);
+ // Send the message to the server.
+ sendMsg(context_.query_);
+ // Expect response. If there is no response, return.
+ context_.response_ = receiveOneMsg();
+ if (!context_.response_) {
+ return;
+ }
+ // If DHCPACK has been returned by the server, use the returned
+ // configuration.
+ if (context_.response_->getType() == DHCPACK) {
+ applyConfiguration();
+ }
+}
+
+void
+Dhcp4Client::doRelease() {
+ // There is no response for Release message.
+ context_.response_.reset();
+
+ if (config_.lease_.addr_.isV4Zero()) {
+ isc_throw(Dhcp4ClientError, "failed to send the release"
+ " message because client doesn't have a lease");
+ }
+ context_.query_ = createMsg(DHCPRELEASE);
+ // Set ciaddr to the address which we want to release.
+ context_.query_->setCiaddr(config_.lease_.addr_);
+ // Include client identifier.
+ appendClientId();
+
+ // Remove configuration.
+ config_.reset();
+
+ // Send the message to the server.
+ sendMsg(context_.query_);
+}
+
+void
+Dhcp4Client::doDecline() {
+ if (config_.lease_.addr_.isV4Zero()) {
+ isc_throw(Dhcp4ClientError, "failed to send the decline"
+ " message because client doesn't have a lease");
+ }
+
+ context_.query_ = createMsg(DHCPDECLINE);
+
+ // Set ciaddr to 0.
+ context_.query_->setCiaddr(IOAddress("0.0.0.0"));
+
+ // Include Requested IP Address Option
+ addRequestedAddress(config_.lease_.addr_);
+
+ // Include client identifier.
+ appendClientId();
+
+ // Include server identifier.
+ appendServerId();
+
+ // Remove configuration.
+ config_.reset();
+
+ // Send the message to the server.
+ sendMsg(context_.query_);
+}
+
+void
+Dhcp4Client::doRequest() {
+ context_.query_ = createMsg(DHCPREQUEST);
+
+ // Override the default ciaddr if specified by a test.
+ if (!ciaddr_.unspecified()) {
+ context_.query_->setCiaddr(ciaddr_);
+ } else if ((state_ == SELECTING) || (state_ == INIT_REBOOT)) {
+ context_.query_->setCiaddr(IOAddress("0.0.0.0"));
+ } else {
+ context_.query_->setCiaddr(IOAddress(config_.lease_.addr_));
+ }
+
+ // Requested IP address.
+ if (state_ == SELECTING) {
+ if (context_.response_ &&
+ (context_.response_->getType() == DHCPOFFER) &&
+ (context_.response_->getYiaddr() != IOAddress("0.0.0.0"))) {
+ addRequestedAddress(context_.response_->getYiaddr());
+ } else {
+ isc_throw(Dhcp4ClientError, "error sending the DHCPREQUEST because"
+ " the received DHCPOFFER message was invalid");
+ }
+ } else if (state_ == INIT_REBOOT) {
+ addRequestedAddress(config_.lease_.addr_);
+ }
+
+ // Server identifier.
+ if (state_ == SELECTING) {
+ if (context_.response_) {
+ OptionPtr opt_serverid =
+ context_.response_->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+ if (!opt_serverid) {
+ isc_throw(Dhcp4ClientError, "missing server identifier in the"
+ " server's response");
+ }
+ context_.query_->addOption(opt_serverid);
+ }
+ }
+
+ // Request options if any.
+ appendPRL();
+ // Include FQDN or Hostname.
+ appendName();
+ // Include Client Identifier
+ appendClientId();
+ // Any other options to be sent by a client.
+ appendExtraOptions();
+ // Add classes.
+ appendClasses();
+ // Send the message to the server.
+ sendMsg(context_.query_);
+ // Expect response.
+ context_.response_ = receiveOneMsg();
+ // If the server has responded, store the configuration received.
+ if (context_.response_) {
+ applyConfiguration();
+ }
+}
+
+void
+Dhcp4Client::receiveResponse() {
+ context_.response_ = receiveOneMsg();
+ // If the server has responded, store the configuration received.
+ if (context_.response_) {
+ applyConfiguration();
+ }
+}
+
+void
+Dhcp4Client::includeClientId(const std::string& clientid) {
+ if (clientid.empty()) {
+ clientid_.reset();
+
+ } else {
+ clientid_ = ClientId::fromText(clientid);
+ }
+}
+
+void
+Dhcp4Client::includeFQDN(const uint8_t flags, const std::string& fqdn_name,
+ Option4ClientFqdn::DomainNameType fqdn_type) {
+ fqdn_.reset(new Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(),
+ fqdn_name, fqdn_type));
+}
+
+void
+Dhcp4Client::includeHostname(const std::string& name) {
+ if (name.empty()) {
+ hostname_.reset();
+ } else {
+ hostname_.reset(new OptionString(Option::V4, DHO_HOST_NAME, name));
+ }
+}
+
+HWAddrPtr
+Dhcp4Client::generateHWAddr(const uint8_t htype) const {
+ if (htype != HTYPE_ETHER) {
+ isc_throw(isc::NotImplemented,
+ "The hardware address type " << static_cast<int>(htype)
+ << " is currently not supported");
+ }
+ std::vector<uint8_t> hwaddr(HWAddr::ETHERNET_HWADDR_LEN);
+ // Generate ethernet hardware address by assigning random byte values.
+ isc::util::fillRandom(hwaddr.begin(), hwaddr.end());
+ return (HWAddrPtr(new HWAddr(hwaddr, htype)));
+}
+
+void
+Dhcp4Client::modifyHWAddr() {
+ if (!hwaddr_) {
+ hwaddr_ = generateHWAddr();
+ return;
+ }
+ // Modify the HW address by adding 1 to its last byte.
+ ++hwaddr_->hwaddr_[hwaddr_->hwaddr_.size() - 1];
+}
+
+void
+Dhcp4Client::requestOption(const uint8_t option) {
+ if (option != 0) {
+ requested_options_.insert(option);
+ }
+}
+
+void
+Dhcp4Client::requestOptions(const uint8_t option1, const uint8_t option2,
+ const uint8_t option3) {
+ requested_options_.clear();
+ requestOption(option1);
+ requestOption(option2);
+ requestOption(option3);
+}
+
+Pkt4Ptr
+Dhcp4Client::receiveOneMsg() {
+ Pkt4Ptr msg = srv_->receiveOneMsg();
+ if (!msg) {
+ return (Pkt4Ptr());
+ }
+
+ // Copy the original message to simulate reception over the wire.
+ msg->pack();
+ Pkt4Ptr msg_copy(new Pkt4(static_cast<const uint8_t*>
+ (msg->getBuffer().getData()),
+ msg->getBuffer().getLength()));
+ msg_copy->setRemoteAddr(msg->getLocalAddr());
+ msg_copy->setLocalAddr(msg->getRemoteAddr());
+ msg_copy->setRemotePort(msg->getLocalPort());
+ msg_copy->setLocalPort(msg->getRemotePort());
+ msg_copy->setIface(msg->getIface());
+ msg_copy->setIndex(msg->getIndex());
+
+ msg_copy->unpack();
+
+ return (msg_copy);
+}
+
+void
+Dhcp4Client::sendMsg(const Pkt4Ptr& msg) {
+ srv_->shutdown_ = false;
+ if (use_relay_) {
+ try {
+ msg->setHops(1);
+ msg->setGiaddr(relay_addr_);
+ msg->setLocalAddr(server_facing_relay_addr_);
+ // Insert RAI
+ OptionPtr rai(new Option(Option::V4, DHO_DHCP_AGENT_OPTIONS));
+ // Insert circuit id, if specified.
+ if (!circuit_id_.empty()) {
+ rai->addOption(OptionPtr(new Option(Option::V4, RAI_OPTION_AGENT_CIRCUIT_ID,
+ OptionBuffer(circuit_id_.begin(),
+ circuit_id_.end()))));
+ }
+ msg->addOption(rai);
+ } catch (...) {
+ // If relay options have already been added in the unittest, ignore
+ // exception on add.
+ }
+ }
+ // Repack the message to simulate wire-data parsing.
+ msg->pack();
+ Pkt4Ptr msg_copy(new Pkt4(static_cast<const uint8_t*>
+ (msg->getBuffer().getData()),
+ msg->getBuffer().getLength()));
+ msg_copy->setRemoteAddr(msg->getLocalAddr());
+ 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 DHCPv4 server does.
+ }
+
+ // Make sure the server processed all packets in MT.
+ isc::util::MultiThreadingMgr::instance().getThreadPool().wait(3);
+}
+
+void
+Dhcp4Client::setHWAddress(const std::string& hwaddr_str) {
+ if (hwaddr_str.empty()) {
+ hwaddr_.reset();
+ } else {
+ hwaddr_.reset(new HWAddr(HWAddr::fromText(hwaddr_str)));
+ }
+}
+
+void
+Dhcp4Client::addExtraOption(const OptionPtr& opt) {
+ extra_options_.insert(std::make_pair(opt->getType(), opt));
+}
+
+void
+Dhcp4Client::addClass(const ClientClass& client_class) {
+ if (!classes_.contains(client_class)) {
+ classes_.insert(client_class);
+ }
+}
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/bin/dhcp4/tests/dhcp4_client.h b/src/bin/dhcp4/tests/dhcp4_client.h
new file mode 100644
index 0000000..a7265a4
--- /dev/null
+++ b/src/bin/dhcp4/tests/dhcp4_client.h
@@ -0,0 +1,533 @@
+// 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 DHCP4_CLIENT_H
+#define DHCP4_CLIENT_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/hwaddr.h>
+#include <dhcp/option.h>
+#include <dhcp/pkt4.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <util/optional.h>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <set>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief General error emitted by the DHCP4 test client.
+class Dhcp4ClientError : public isc::Exception {
+public:
+ Dhcp4ClientError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief DHCPv4 client used for unit testing.
+///
+/// This class implements a DHCPv4 "client" which interoperates with the
+/// @c NakedDhcpv4Srv class. It calls @c NakedDhcpv4Srv::fakeReceive to
+/// deliver client messages to the server for processing. The server places
+/// the response in the @c NakedDhcpv4Srv::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.
+///
+/// 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.
+class Dhcp4Client : public boost::noncopyable {
+public:
+
+ /// @brief States of the DHCP client.
+ enum State {
+ SELECTING,
+ INIT_REBOOT,
+ RENEWING,
+ REBINDING
+ };
+
+ /// @brief Holds the DHCPv4 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.
+ Pkt4Ptr query_;
+ /// @brief Holds the last sent message by the server to the client.
+ Pkt4Ptr response_;
+ };
+
+ /// @brief Holds the configuration of the client received from the
+ /// DHCP server.
+ struct Configuration {
+ /// @brief Holds IP addresses received in the Routers option.
+ Option4AddrLst::AddressContainer routers_;
+ /// @brief Holds IP addresses received in the DNS Servers option.
+ Option4AddrLst::AddressContainer dns_servers_;
+ /// @brief Holds IP addresses received in the Log Servers option.
+ Option4AddrLst::AddressContainer log_servers_;
+ /// @brief Holds IP addresses received in the Quotes Servers option.
+ Option4AddrLst::AddressContainer quotes_servers_;
+ /// @brief Vendor Specific options
+ OptionCollection vendor_suboptions_;
+ /// @brief Holds a lease obtained by the client.
+ Lease4 lease_;
+ /// @brief Holds server id of the server which responded to the client's
+ /// request.
+ asiolink::IOAddress serverid_;
+ /// @brief Holds returned siaddr.
+ asiolink::IOAddress siaddr_;
+ /// @brief Holds returned sname.
+ std::string sname_;
+ /// @brief Holds returned (boot)file.
+ std::string boot_file_name_;
+
+ /// @brief Constructor.
+ Configuration();
+
+ /// @brief Sets configuration values to defaults.
+ void reset();
+ };
+
+ /// @brief Creates a new client.
+ ///
+ /// @param Initial client's state.
+ Dhcp4Client(const State& state = SELECTING);
+
+ /// @brief Creates a new client that communicates with a specified server.
+ ///
+ /// @param srv An instance of the DHCPv4 server to be used.
+ /// @param state Initial client's state.
+ Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv> srv,
+ const State& state = SELECTING);
+
+ /// @brief Creates a lease for the client using the specified address
+ /// and valid lifetime.
+ ///
+ /// This method creates the lease using the specified address and
+ /// valid lease lifetime. The client will use this lease in any
+ /// future communication with the DHCP server. One of the use cases
+ /// for this method is to pre-configure the client with the explicitly
+ /// given address before it sends the DHCPINFORM to the DHCP server.
+ /// The client will inject the leased address into the ciaddr field
+ /// of the DHCPINFORM message.
+ ///
+ /// @param addr Lease address.
+ /// @param valid_lft Valid lifetime.
+ void createLease(const asiolink::IOAddress& addr, const uint32_t valid_lft);
+
+ /// @brief Sends DHCPDISCOVER message to the server and receives response.
+ ///
+ /// The message being sent to the server includes Parameter Request List
+ /// option if any options to be requested have been specified using the
+ /// @c requestOptions or @c requestOption methods.
+ ///
+ /// The configuration returned by the server in the DHCPOFFER message is
+ /// NOT stored in the client configuration: @c config_.
+ ///
+ /// @param requested_addr A pointer to the IP Address to be sent in the
+ /// Requested IP Address option or NULL if the option should not be
+ /// included.
+ void doDiscover(const boost::shared_ptr<asiolink::IOAddress>&
+ requested_addr = boost::shared_ptr<asiolink::IOAddress>());
+
+ /// @brief Perform 4-way exchange with a server.
+ ///
+ /// This method calls @c doDiscover and @c doRequest to perform the 4-way
+ /// exchange with the server.
+ ///
+ /// @param requested_addr A pointer to the address to be requested using the
+ /// Requested IP Address option.
+ void doDORA(const boost::shared_ptr<asiolink::IOAddress>&
+ requested_addr = boost::shared_ptr<asiolink::IOAddress>());
+
+ /// @brief Sends DHCPINFORM message to the server and receives response.
+ ///
+ /// This function simulates sending the DHCPINFORM message to the server
+ /// and receiving server's response (if any). The server's response and the
+ /// message sent to the server is stored in the context structure and can
+ /// be accessed using @c getContext function.
+ ///
+ /// The configuration returned by the server is stored in the
+ /// @c config_ public member and can be accessed directly.
+ ///
+ /// @param set_ciaddr Indicates if the ciaddr should be set for an
+ /// outgoing message and defaults to true. Note, that the RFC2131 mandates
+ /// setting the ciaddr for DHCPINFORM but the server may still want to
+ /// respond if the ciaddr is not set.
+ ///
+ /// @throw This function doesn't thrown exceptions on its own, but it calls
+ /// functions that are not exception safe, so it may emit an exception if
+ /// an error occurs.
+ void doInform(const bool set_ciaddr = true);
+
+ /// @brief Sends DHCPRELEASE Message to the server.
+ ///
+ /// This method simulates sending the DHCPRELEASE message to the server.
+ /// The released lease is removed from the client's configuration.
+ void doRelease();
+
+
+ /// @brief Sends DHCPDECLINE Message to the server.
+ ///
+ /// This method simulates sending the DHCPDECLINE message to the server.
+ /// The released lease is removed from the client's configuration.
+ void doDecline();
+
+ /// @brief Sends DHCPREQUEST Message to the server and receives a response.
+ ///
+ /// This method simulates sending the DHCPREQUEST message to the server and
+ /// receiving a response. The DHCPREQUEST message can be used by the client
+ /// being in various states:
+ /// - SELECTING - client is trying to obtain a new lease and it has selected
+ /// the server using the DHCPDISCOVER.
+ /// - INIT-REBOOT - client cached an address it was previously using and is
+ /// now trying to verify if this address is still valid.
+ /// - RENEW - client's renewal timer has passed and the client is trying to
+ /// extend the lifetime of the lease.
+ /// - REBIND - client's rebind timer has passed and the client is trying to
+ /// extend the lifetime of the lease from any server.
+ ///
+ /// Depending on the state that the client is in, different combinations of
+ /// - ciaddr
+ /// - Requested IP Address option
+ /// - server identifier
+ /// are used (as per RFC2131, section 4.3.2). Therefore, the unit tests
+ /// must set the appropriate state of the client prior to calling this
+ /// method using the @c setState function.
+ ///
+ /// When the server returns the DHCPACK the configuration carried in the
+ /// DHCPACK message is applied and can be obtained from the @c config_.
+ void doRequest();
+
+ /// @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();
+
+ /// @brief Generates a hardware address used by the client.
+ ///
+ /// It assigns random values to the bytes of the hardware address.
+ ///
+ /// @param htype hardware address type. Currently the only type
+ /// supported is Ethernet hardware address.
+ ///
+ /// @return Pointer to the generated hardware address.
+ HWAddrPtr generateHWAddr(const uint8_t htype = HTYPE_ETHER) const;
+
+ /// @brief Returns HW address used by the client.
+ HWAddrPtr getHWAddress() const {
+ return (hwaddr_);
+ }
+
+ /// @brief Returns current context.
+ const Context& getContext() const {
+ return (context_);
+ }
+
+ /// @brief Returns the server that the client is communicating with.
+ boost::shared_ptr<NakedDhcpv4Srv> getServer() const {
+ return (srv_);
+ }
+
+ /// @brief Creates the client id from the client id in the textual format.
+ ///
+ /// The generated client id will be added to the client's messages to the
+ /// server.
+ ///
+ /// @param clientid Client id in the textual format. Use the empty client id
+ /// value to not include the client id.
+ void includeClientId(const std::string& clientid);
+
+ /// @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 includeFQDN(const uint8_t flags, const std::string& fqdn_name,
+ Option4ClientFqdn::DomainNameType fqdn_type);
+
+ /// @brief Creates an instance of the Hostname option to be included
+ /// in the client's message.
+ ///
+ /// @param name Name to be stored in the option.
+ void includeHostname(const std::string& name);
+
+ /// @brief Modifies the client's HW address (adds one to it).
+ ///
+ /// The HW address should be modified to test negative scenarios when the
+ /// client acquires a lease and tries to renew it with a different HW
+ /// address. The server should detect the HW address mismatch and react
+ /// accordingly.
+ ///
+ /// The HW address modification affects the value returned by the
+ /// @c Dhcp4Client::getHWAddress.
+ void modifyHWAddr();
+
+ /// @brief Specify an option to be requested by a client.
+ ///
+ /// This function adds option code to the collection of option
+ /// codes to be requested by a client.
+ ///
+ /// @param option Option code to be requested. The value of 0 is
+ /// ignored and the function is no-op.
+ void requestOption(const uint8_t option);
+
+ /// @brief Specifies options to be requested by the client.
+ ///
+ /// This function configures the client to request options having
+ /// specified codes using Parameter Request List option. The default
+ /// value of 0 specify that the option is not requested.
+ ///
+ /// If there are options specified to be requested before the function
+ /// is called, the new option codes override previously specified ones.
+ /// In order to clear the list of requested options call
+ /// @c requestOptions(0).
+ ///
+ /// @param option1 First option to be requested.
+ /// @param option2 Second option to be requested (optional).
+ /// @param option3 Third option to be requested (optional).
+ void requestOptions(const uint8_t option1,
+ const uint8_t option2 = 0,
+ const uint8_t option3 = 0);
+
+ /// @brief Sets circuit-id value to be included in the circuit-id
+ /// sub option of the RAI option.
+ ///
+ /// @param circuit_id New circuit-id value.
+ void setCircuitId(const std::string& circuit_id) {
+ circuit_id_ = circuit_id;
+ }
+
+ /// @brief Sets destination address for the messages being sent by the
+ /// client.
+ ///
+ /// By default, the client uses broadcast address 255.255.255.255 to
+ /// communicate with the server. In certain cases it may be desired
+ /// that different address is used. 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 explicit hardware address for the client.
+ ///
+ /// @param hwaddr_str String representation of the HW address. Use an
+ /// empty string to set the NULL hardware address.
+ void setHWAddress(const std::string& hwaddr_str);
+
+ /// @brief Sets the interface over which the messages should be sent.
+ ///
+ /// @param iface_name Name of the interface over which the messages should
+ /// be sent.
+ void setIfaceName(const std::string& iface_name) {
+ iface_name_ = iface_name;
+ }
+
+ /// @brief Sets the interface over which the messages should be sent.
+ ///
+ /// @param iface_index Index of the interface over which the
+ /// messages should be sent.
+ void setIfaceIndex(uint32_t iface_index) {
+ iface_index_ = iface_index;
+ }
+
+ /// @brief Sets client state.
+ ///
+ /// Depending on the current state the client's behavior is different
+ /// when sending Request messages as per RFC2131, section 4.3.2.
+ ///
+ /// @param state New client's state.
+ void setState(const State& state) {
+ state_ = state;
+ }
+
+ /// @brief Simulate sending messages through a relay.
+ ///
+ /// @param use Parameter which 'true' value indicates that client should
+ /// simulate sending messages via relay.
+ /// @param relay_addr Relay address
+ /// @param sf_relay_addr Server facing relay address.
+ void useRelay(const bool use = true,
+ const asiolink::IOAddress& relay_addr =
+ asiolink::IOAddress("192.0.2.2"),
+ const asiolink::IOAddress& sf_relay_addr =
+ asiolink::IOAddress("10.0.0.2")) {
+ use_relay_ = use;
+ relay_addr_ = relay_addr;
+ server_facing_relay_addr_ = sf_relay_addr;
+ }
+
+ /// @brief Current client's configuration obtained from the server.
+ Configuration config_;
+
+ /// @brief Specific ciaddr to be used in client's messages.
+ ///
+ /// If this value is "unspecified" the default values will be used
+ /// by the client. If this value is specified, it will override ciaddr
+ /// in the client's messages.
+ isc::util::Optional<asiolink::IOAddress> ciaddr_;
+
+ /// @brief Adds extra option (an option the client will always send)
+ ///
+ /// @param opt additional option to be sent
+ void addExtraOption(const OptionPtr& opt);
+
+ /// @brief Add a client class.
+ ///
+ /// @param client_class name of the class to be added.
+ void addClass(const ClientClass& client_class);
+
+private:
+ /// @brief Appends extra options, previously added with addExtraOption()
+ ///
+ /// @brief Copies options from extra_options_ into outgoing message
+ void appendExtraOptions();
+
+ /// @brief Appends extra classes, previously added with addClass()
+ ///
+ /// @brief Add client classes from classes_ to incoming message
+ void appendClasses();
+
+ /// @brief Creates and adds Requested IP Address option to the client's
+ /// query.
+ ///
+ /// @param addr Address to be added in the Requested IP Address option.
+ void addRequestedAddress(const asiolink::IOAddress& addr);
+
+ /// @brief Stores configuration received from the server.
+ ///
+ /// This methods stores the configuration obtained from the DHCP server
+ /// in the @c Configuration structure. This configuration includes:
+ /// - obtained lease
+ /// - server id of the server that provided the configuration
+ /// - lease
+ /// - selected options (used by unit tests):
+ /// - DNS Servers
+ /// - Routers
+ /// - Log Servers
+ /// - Quotes Servers
+ void applyConfiguration();
+
+ /// @brief Creates client's side DHCP message.
+ ///
+ /// @param msg_type Type of the message to be created.
+ /// @return An instance of the message created.
+ Pkt4Ptr createMsg(const uint8_t msg_type);
+
+ /// @brief Includes the Client Identifier option in the client's message.
+ ///
+ /// This function creates an instance of the Client Identifier option
+ /// if the client identifier has been specified and includes this
+ /// option in the client's message to the server.
+ void appendClientId();
+
+ /// @brief Includes the Server Identifier option in the client's message.
+ ///
+ /// This function creates an instance of the Server Identifier option.
+ /// It uses whatever information is stored in config_.serverid_.
+ void appendServerId();
+
+ /// @brief Includes FQDN or Hostname option in the client's message.
+ ///
+ /// This method checks if @c fqdn_ or @c hostname_ is specified and
+ /// includes it in the client's message. If both are specified, the
+ /// @c fqdn_ will be used.
+ void appendName();
+
+ /// @brief Include PRL Option in the query message.
+ ///
+ /// This function creates the instance of the PRL (Parameter Request List)
+ /// option and adds option codes from the @c requested_options_ to it.
+ /// It later adds the PRL option to the @c context_.query_ message
+ /// if it is non-NULL.
+ void appendPRL();
+
+ /// @brief Simulates reception of the message from the server.
+ ///
+ /// @return Received message.
+ Pkt4Ptr 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 Pkt4Ptr& msg);
+
+ /// @brief Current context (sent and received message).
+ Context context_;
+
+ /// @biref Current transaction id (altered on each send).
+ uint32_t curr_transid_;
+
+ /// @brief Currently used destination address.
+ asiolink::IOAddress dest_addr_;
+
+ /// @brief FQDN requested by the client.
+ Option4ClientFqdnPtr fqdn_;
+
+ /// @brief Hostname requested by the client.
+ OptionStringPtr hostname_;
+
+ /// @brief Current hardware address of the client.
+ HWAddrPtr hwaddr_;
+
+ /// @brief Current client identifier.
+ ClientIdPtr clientid_;
+
+ /// @brief Interface to be used to send the messages (name).
+ std::string iface_name_;
+
+ /// @brief Interface to be used to send the messages (index).
+ uint32_t iface_index_;
+
+ /// @brief Relay address to use.
+ asiolink::IOAddress relay_addr_;
+
+ /// @brief Collection of options codes to be requested by the client.
+ std::set<uint8_t> requested_options_;
+
+ /// @brief Address of the relay interface connected to the server.
+ asiolink::IOAddress server_facing_relay_addr_;
+
+ /// @brief Pointer to the server that the client is communicating with.
+ boost::shared_ptr<NakedDhcpv4Srv> srv_;
+
+ /// @brief Current state of the client.
+ State state_;
+
+ /// @brief Enable relaying messages to the server.
+ bool use_relay_;
+
+ /// @brief Specifies value to be inserted into circuit-id sub option
+ /// of the RAI option.
+ std::string circuit_id_;
+
+ /// @brief Extra options the client will send.
+ OptionCollection extra_options_;
+
+ /// @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 // DHCP4_CLIENT_H
diff --git a/src/bin/dhcp4/tests/dhcp4_process_tests.sh.in b/src/bin/dhcp4/tests/dhcp4_process_tests.sh.in
new file mode 100644
index 0000000..8cc8d93
--- /dev/null
+++ b/src/bin/dhcp4/tests/dhcp4_process_tests.sh.in
@@ -0,0 +1,477 @@
+#!/bin/sh
+
+# Copyright (C) 2014-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/.
+
+# 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/dhcp4/tests/test_config.json"
+# Path to the Kea log file.
+LOG_FILE="@abs_top_builddir@/src/bin/dhcp4/tests/test.log"
+# Path to the Kea lease file.
+LEASE_FILE="@abs_top_builddir@/src/bin/dhcp4/tests/test_leases.csv"
+# Path to the Kea LFC application
+export KEA_LFC_EXECUTABLE="@abs_top_builddir@/src/bin/lfc/kea-lfc"
+# Kea configuration to be stored in the configuration file.
+CONFIG="{
+ \"Dhcp4\":
+ {
+ \"interfaces-config\": {
+ \"interfaces\": [ ]
+ },
+ \"valid-lifetime\": 4000,
+ \"renew-timer\": 1000,
+ \"rebind-timer\": 2000,
+ \"lease-database\":
+ {
+ \"type\": \"memfile\",
+ \"name\": \"$LEASE_FILE\",
+ \"persist\": false,
+ \"lfc-interval\": 0
+ },
+ \"subnet4\": [
+ {
+ \"subnet\": \"10.0.0.0/8\",
+ \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ]
+ } ],
+ \"dhcp-ddns\": {
+ \"enable-updates\": true,
+ \"qualifying-suffix\": \"\"
+ },
+ \"loggers\": [
+ {
+ \"name\": \"kea-dhcp4\",
+ \"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 subnet4/pools, no subnet4/pool
+CONFIG_BAD_SYNTAX="{
+ \"Dhcp4\":
+ {
+ \"interfaces\": [ ],
+ \"valid-lifetime\": 4000,
+ \"renew-timer\": 1000,
+ \"rebind-timer\": 2000,
+ \"lease-database\":
+ {
+ \"type\": \"memfile\",
+ \"persist\": false
+ },
+ \"subnet4\": [
+ {
+ \"subnet\": \"10.0.0.0/8\",
+ \"pool\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ]
+ } ],
+ \"loggers\": [
+ {
+ \"name\": \"kea-dhcp4\",
+ \"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="{
+ \"Dhcp4\":
+ {
+ \"interfaces-config\": {
+ \"interfaces\": [ ]
+ },
+ \"valid-lifetime\": 4000,
+ \"renew-timer\": 1000,
+ \"rebind-timer\": 2000,
+ \"lease-database\":
+ {
+ \"type\": \"memfile\",
+ \"persist\": false
+ },
+ \"subnet4\": [
+ {
+ \"subnet\": \"10.0.0.0/8\",
+ \"pools\": [ { \"pool\": \"192.168.0.10-192.168.0.100\" } ]
+ } ],
+ \"loggers\": [
+ {
+ \"name\": \"kea-dhcp4\",
+ \"output_options\": [
+ {
+ \"output\": \"$LOG_FILE\"
+ }
+ ],
+ \"severity\": \"INFO\"
+ }
+ ]
+ }
+}"
+
+# Invalid configuration (negative valid-lifetime) to check that Kea
+# gracefully handles reconfiguration errors.
+CONFIG_INVALID="{
+ \"Dhcp4\":
+ {
+ \"interfaces-config\": {
+ \"interfaces\": [ ]
+ },
+ \"valid-lifetime\": -3,
+ \"renew-timer\": 1000,
+ \"rebind-timer\": 2000,
+ \"lease-database\":
+ {
+ \"type\": \"memfile\",
+ \"persist\": false
+ },
+ \"subnet4\": [
+ {
+ \"subnet\": \"10.0.0.0/8\",
+ \"pool\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ]
+ } ],
+ \"loggers\": [
+ {
+ \"name\": \"kea-dhcp4\",
+ \"output_options\": [
+ {
+ \"output\": \"$LOG_FILE\"
+ }
+ ],
+ \"severity\": \"INFO\"
+ }
+ ]
+ }
+}"
+
+# Set the location of the executable.
+bin="kea-dhcp4"
+bin_path="@abs_top_builddir@/src/bin/dhcp4"
+
+# 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}"
+
+ # 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} -t ${CFG_FILE}\""
+ run_command \
+ "${bin_path}/${bin}" -t "${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 DHCPv4 can be reconfigured with a SIGHUP signal.
+dynamic_reconfiguration_test() {
+ # Log the start of the test and print test name.
+ test_start "dhcpv4_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 "DHCP4_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 DHCP4_CONFIG_COMPLETE messages.
+ # Wait for it up to 10s.
+ wait_for_message 10 "DHCP4_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 "DHCP4_PACKET_RECEIVE_FAIL"
+ assert_eq 0 "${_GET_LOG_MESSAGES}" \
+ "Expected get_log_messages DHCP4_PACKET_RECEIVE_FAIL return %d, \
+returned %d."
+
+ # All ok. Shut down Kea and exit.
+ test_finish 0
+}
+
+# This test verifies that DHCPv4 server is shut down gracefully when it
+# receives a SIGINT or SIGTERM signal.
+shutdown_test() {
+ test_name=${1} # Test name
+ 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 "DHCP4_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 "DHCP4_PACKET_RECEIVE_FAIL"
+ assert_eq 0 "${_GET_LOG_MESSAGES}" \
+ "Expected get_log_messages return %d, returned %d."
+
+ test_finish 0
+}
+
+# This test verifies that DHCPv4 can be configured to run lease file cleanup
+# periodically.
+lfc_timer_test() {
+ # Log the start of the test and print test name.
+ test_start "dhcpv4_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 "DHCP4_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 "DHCP4_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}" DHCP4_ALREADY_RUNNING
+dynamic_reconfiguration_test
+shutdown_test "dhcpv4.sigterm_test" 15
+shutdown_test "dhcpv4.sigint_test" 2
+version_test "dhcpv4.version"
+logger_vars_test "dhcpv4.variables"
+lfc_timer_test
+syntax_check_test "dhcpv4.syntax_check_success" "${CONFIG}" 0
+syntax_check_test "dhcpv4.syntax_check_bad_syntax" "${CONFIG_BAD_SYNTAX}" 1
+syntax_check_test "dhcpv4.syntax_check_bad_values" "${CONFIG_BAD_VALUES}" 1
+password_redact_test "dhcpv4.password_redact_test" "$(kea_dhcp_config 4)" 0
diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
new file mode 100644
index 0000000..4e5f05f
--- /dev/null
+++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
@@ -0,0 +1,4855 @@
+// Copyright (C) 2011-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 <sstream>
+
+#include <asiolink/io_address.h>
+#include <cc/command_interpreter.h>
+#include <config/command_mgr.h>
+#include <config_backend/base_config_backend.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp4/tests/dhcp4_client.h>
+#include <dhcp/tests/pkt_captures.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/pkt_filter.h>
+#include <dhcp/pkt_filter_inet.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp4/dhcp4_srv.h>
+#include <dhcp4/dhcp4_log.h>
+#include <dhcp4/json_config_parser.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/utils.h>
+#include <dhcpsrv/host_mgr.h>
+#include <stats/stats_mgr.h>
+#include <testutils/gtest_utils.h>
+#include <util/encode/hex.h>
+#include <boost/scoped_ptr.hpp>
+
+#include <iostream>
+#include <cstdlib>
+#include <dirent.h>
+
+#include <arpa/inet.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::asiolink;
+using namespace isc::cb;
+using namespace isc::config;
+using namespace isc::dhcp::test;
+using namespace isc::util;
+
+namespace {
+
+const char* CONFIGS[] = {
+ // Configuration 0:
+ // - 1 subnet: 10.254.226.0/25
+ // - used for recorded traffic (see PktCaptures::captureRelayedDiscover)
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"10.254.226.0/25\" } ],"
+ " \"subnet\": \"10.254.226.0/24\", "
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"valid-lifetime\": 4000,"
+ " \"interface\": \"eth0\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+ // Configuration 1:
+ // - 1 subnet: 192.0.2.0/24
+ // - MySQL Host Data Source configured
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"hosts-database\": {"
+ " \"type\": \"mysql\","
+ " \"name\": \"keatest\","
+ " \"user\": \"keatest\","
+ " \"password\": \"keatest\""
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"valid-lifetime\": 4000,"
+ " \"interface\": \"eth0\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+ // Configuration 2:
+ // - 1 subnet, 2 global options (one forced with always-send)
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"valid-lifetime\": 4000, "
+ " \"subnet4\": [ {"
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ], "
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"default-ip-ttl\", "
+ " \"data\": \"FF\", "
+ " \"csv-format\": false"
+ " }, "
+ " {"
+ " \"name\": \"ip-forwarding\", "
+ " \"data\": \"false\", "
+ " \"always-send\": true"
+ " }"
+ " ]"
+ "}",
+
+ // Configuration 3:
+ // - one subnet, with one pool
+ // - user-contexts defined in both subnet and pool
+ "{"
+ " \"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"10.254.226.0/25\","
+ " \"user-context\": { \"value\": 42 } } ],"
+ " \"subnet\": \"10.254.226.0/24\", "
+ " \"user-context\": {"
+ " \"secure\": false"
+ " }"
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+};
+
+// Convenience function for comparing option buffer to an expected string value
+// @param exp_string expected string value
+// @param buffer OptionBuffer whose contents are to be tested
+void checkStringInBuffer( const std::string& exp_string, const OptionBuffer& buffer) {
+ std::string buffer_string(buffer.begin(), buffer.end());
+ EXPECT_EQ(exp_string, std::string(buffer_string.c_str()));
+}
+
+// This test verifies that the destination address of the response
+// message is set to giaddr, when giaddr is set to non-zero address
+// in the received message.
+TEST_F(Dhcpv4SrvTest, adjustIfaceDataRelay) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Create the instance of the incoming packet.
+ boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
+ // Set the giaddr to non-zero address and hops to non-zero value
+ // as if it was relayed.
+ req->setGiaddr(IOAddress("192.0.1.1"));
+ req->setHops(2);
+ // Set ciaddr to zero. This simulates the client which applies
+ // for the new lease.
+ req->setCiaddr(IOAddress("0.0.0.0"));
+ // Clear broadcast flag.
+ req->setFlags(0x0000);
+
+ // Set local address, port and interface.
+ req->setLocalAddr(IOAddress("192.0.2.5"));
+ req->setLocalPort(1001);
+ req->setIface("eth1");
+ req->setIndex(ETH1_INDEX);
+
+ // Set remote port (it will be used in the next test).
+ req->setRemotePort(1234);
+
+ // Create the exchange using the req.
+ Dhcpv4Exchange ex = createExchange(req);
+
+ Pkt4Ptr resp = ex.getResponse();
+ resp->setYiaddr(IOAddress("192.0.1.100"));
+ // Clear the remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+ // Set hops value for the response.
+ resp->setHops(req->getHops());
+
+ // This function never throws.
+ ASSERT_NO_THROW(srv_.adjustIfaceData(ex));
+
+ // Now the destination address should be relay's address.
+ EXPECT_EQ("192.0.1.1", resp->getRemoteAddr().toText());
+ // The query has been relayed, so the response must be sent to the port 67.
+ EXPECT_EQ(DHCP4_SERVER_PORT, resp->getRemotePort());
+ // Local address should be the address assigned to interface eth1.
+ EXPECT_EQ("192.0.2.5", resp->getLocalAddr().toText());
+ // The local port is always DHCPv4 server port 67.
+ EXPECT_EQ(DHCP4_SERVER_PORT, resp->getLocalPort());
+ // We will send response over the same interface which was used to receive
+ // query.
+ EXPECT_EQ("eth1", resp->getIface());
+ EXPECT_EQ(ETH1_INDEX, resp->getIndex());
+
+ // Let's do another test and set other fields: ciaddr and
+ // flags. By doing it, we want to make sure that the relay
+ // address will take precedence.
+ req->setGiaddr(IOAddress("192.0.1.50"));
+ req->setCiaddr(IOAddress("192.0.1.11"));
+ req->setFlags(Pkt4::FLAG_BROADCAST_MASK);
+
+ resp->setYiaddr(IOAddress("192.0.1.100"));
+ // Clear remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+
+ // Set the client and server ports.
+ srv_.client_port_ = 1234;
+ srv_.server_port_ = 2345;
+
+ ASSERT_NO_THROW(srv_.adjustIfaceData(ex));
+
+ // Response should be sent back to the relay address.
+ EXPECT_EQ("192.0.1.50", resp->getRemoteAddr().toText());
+
+ // Remote port was enforced to the client port.
+ EXPECT_EQ(srv_.client_port_, resp->getRemotePort());
+
+ // Local port was enforced to the server port.
+ EXPECT_EQ(srv_.server_port_, resp->getLocalPort());
+}
+
+// This test verifies that the remote port is adjusted when
+// the query carries a relay port RAI sub-option.
+TEST_F(Dhcpv4SrvTest, adjustIfaceDataRelayPort) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Create the instance of the incoming packet.
+ boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
+ // Set the giaddr to non-zero address and hops to non-zero value
+ // as if it was relayed.
+ req->setGiaddr(IOAddress("192.0.1.1"));
+ req->setHops(2);
+ // Set ciaddr to zero. This simulates the client which applies
+ // for the new lease.
+ req->setCiaddr(IOAddress("0.0.0.0"));
+ // Clear broadcast flag.
+ req->setFlags(0x0000);
+
+ // Set local address, port and interface.
+ req->setLocalAddr(IOAddress("192.0.2.5"));
+ req->setLocalPort(1001);
+ req->setIface("eth1");
+ req->setIndex(ETH1_INDEX);
+
+ // Set remote port.
+ req->setRemotePort(1234);
+
+ // Add a RAI relay-port sub-option (the only difference with the previous test).
+ OptionDefinitionPtr rai_def =
+ LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, DHO_DHCP_AGENT_OPTIONS);
+ ASSERT_TRUE(rai_def);
+ OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4));
+ ASSERT_TRUE(rai);
+ req->addOption(rai);
+ OptionPtr relay_port(new Option(Option::V4, RAI_OPTION_RELAY_PORT));
+ ASSERT_TRUE(relay_port);
+ rai->addOption(relay_port);
+
+ // Create the exchange using the req.
+ Dhcpv4Exchange ex = createExchange(req);
+
+ Pkt4Ptr resp = ex.getResponse();
+ resp->setYiaddr(IOAddress("192.0.1.100"));
+ // Clear the remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+ // Set hops value for the response.
+ resp->setHops(req->getHops());
+
+ // Set the remote port to 67 as we know it will be updated.
+ resp->setRemotePort(67);
+
+ // This function never throws.
+ ASSERT_NO_THROW(srv_.adjustIfaceData(ex));
+
+ // Now the destination address should be relay's address.
+ EXPECT_EQ("192.0.1.1", resp->getRemoteAddr().toText());
+ // The query has been relayed, so the response should be sent to the
+ // port 67, but here there is a relay port RAI so another value is used.
+ EXPECT_EQ(1234, resp->getRemotePort());
+ // Local address should be the address assigned to interface eth1.
+ EXPECT_EQ("192.0.2.5", resp->getLocalAddr().toText());
+ // The local port is always DHCPv4 server port 67.
+ EXPECT_EQ(DHCP4_SERVER_PORT, resp->getLocalPort());
+ // We will send response over the same interface which was used to receive
+ // query.
+ EXPECT_EQ("eth1", resp->getIface());
+ EXPECT_EQ(ETH1_INDEX, resp->getIndex());
+}
+
+// This test verifies that it is possible to configure the server to use
+// routing information to determine the right outbound interface to sent
+// responses to a relayed client.
+TEST_F(Dhcpv4SrvTest, adjustIfaceDataUseRouting) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Create configuration for interfaces. It includes the outbound-interface
+ // setting which indicates that the responses aren't necessarily sent
+ // over the same interface via which a request has been received, but routing
+ // information is used to determine this interface.
+ CfgMgr::instance().clear();
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ cfg_iface->useSocketType(AF_INET, CfgIface::SOCKET_UDP);
+ cfg_iface->use(AF_INET, "eth0");
+ cfg_iface->use(AF_INET, "eth1");
+ cfg_iface->setOutboundIface(CfgIface::USE_ROUTING);
+ CfgMgr::instance().commit();;
+
+ // Create the instance of the incoming packet.
+ boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
+ // Set the giaddr to non-zero address and hops to non-zero value
+ // as if it was relayed.
+ req->setGiaddr(IOAddress("192.0.1.1"));
+ req->setHops(2);
+ // Set ciaddr to zero. This simulates the client which applies
+ // for the new lease.
+ req->setCiaddr(IOAddress("0.0.0.0"));
+ // Clear broadcast flag.
+ req->setFlags(0x0000);
+
+ // Set local address, port and interface.
+ req->setLocalAddr(IOAddress("192.0.2.5"));
+ req->setLocalPort(1001);
+ req->setIface("eth1");
+ req->setIndex(ETH1_INDEX);
+
+ // Create the exchange using the req.
+ Dhcpv4Exchange ex = createExchange(req);
+
+ Pkt4Ptr resp = ex.getResponse();
+ resp->setYiaddr(IOAddress("192.0.1.100"));
+ // Clear the remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+ // Set hops value for the response.
+ resp->setHops(req->getHops());
+
+ // This function never throws.
+ ASSERT_NO_THROW(srv_.adjustIfaceData(ex));
+
+ // Now the destination address should be relay's address.
+ EXPECT_EQ("192.0.1.1", resp->getRemoteAddr().toText());
+ // The query has been relayed, so the response must be sent to the port 67.
+ EXPECT_EQ(DHCP4_SERVER_PORT, resp->getRemotePort());
+
+ // The local port is always DHCPv4 server port 67.
+ EXPECT_EQ(DHCP4_SERVER_PORT, resp->getLocalPort());
+
+ // No specific interface is selected as outbound interface and no specific
+ // local address is provided. The IfaceMgr will figure out which interface to use.
+ EXPECT_TRUE(resp->getLocalAddr().isV4Zero());
+ EXPECT_FALSE(resp->indexSet());
+
+ // Fixed in #5515 so now the interface name is never empty.
+ EXPECT_FALSE(resp->getIface().empty());
+
+ // Another test verifies that setting outbound interface to same as inbound will
+ // cause the server to set interface and local address as expected.
+
+ cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ cfg_iface->useSocketType(AF_INET, CfgIface::SOCKET_UDP);
+ cfg_iface->use(AF_INET, "eth0");
+ cfg_iface->use(AF_INET, "eth1");
+ cfg_iface->setOutboundIface(CfgIface::SAME_AS_INBOUND);
+ CfgMgr::instance().commit();
+
+ ASSERT_NO_THROW(srv_.adjustIfaceData(ex));
+
+ EXPECT_EQ("192.0.2.5", resp->getLocalAddr().toText());
+ EXPECT_EQ("eth1", resp->getIface());
+ EXPECT_EQ(ETH1_INDEX, resp->getIndex());
+}
+
+// This test verifies that the destination address of the response
+// message is set to source address when the testing mode is enabled.
+// Relayed message: not testing mode was tested in adjustIfaceDataRelay.
+TEST_F(Dhcpv4SrvTest, adjustRemoteAddressRelaySendToSourceTestingModeEnabled) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Create the instance of the incoming packet.
+ boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
+ // Set the giaddr to non-zero address and hops to non-zero value
+ // as if it was relayed.
+ req->setGiaddr(IOAddress("192.0.1.1"));
+ req->setHops(2);
+ // Set ciaddr to zero. This simulates the client which applies
+ // for the new lease.
+ req->setCiaddr(IOAddress("0.0.0.0"));
+ // Clear broadcast flag.
+ req->setFlags(0x0000);
+
+ // Set local address, port and interface.
+ req->setLocalAddr(IOAddress("192.0.2.5"));
+ req->setLocalPort(1001);
+ req->setIface("eth1");
+ req->setIndex(ETH1_INDEX);
+
+ // Set remote address and port.
+ req->setRemoteAddr(IOAddress("192.0.2.1"));
+ req->setRemotePort(1234);
+
+ // Create the exchange using the req.
+ Dhcpv4Exchange ex = createExchange(req);
+
+ Pkt4Ptr resp = ex.getResponse();
+ resp->setYiaddr(IOAddress("192.0.1.100"));
+ // Clear the remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+ // Set hops value for the response.
+ resp->setHops(req->getHops());
+
+ // Set the testing mode.
+ srv_.setSendResponsesToSource(true);
+
+ // This function never throws.
+ ASSERT_NO_THROW(srv_.adjustIfaceData(ex));
+
+ // Now the destination address should be source address.
+ EXPECT_EQ("192.0.2.1", resp->getRemoteAddr().toText());
+}
+
+// This test verifies that the destination address of the response message
+// is set to ciaddr when giaddr is set to zero and the ciaddr is set to
+// non-zero address in the received message. This is the case when the
+// client is in Renew or Rebind state.
+TEST_F(Dhcpv4SrvTest, adjustIfaceDataRenew) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Create instance of the incoming packet.
+ boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Clear giaddr to simulate direct packet.
+ req->setGiaddr(IOAddress("0.0.0.0"));
+ // Set ciaddr to non-zero address. The response should be sent to this
+ // address as the client is in renewing or rebinding state (it is fully
+ // configured).
+ req->setCiaddr(IOAddress("192.0.1.15"));
+ // Let's configure broadcast flag. It should be ignored because
+ // we are responding directly to the client having an address
+ // and trying to extend his lease. Broadcast flag is only used
+ // when new lease is acquired and server must make a decision
+ // whether to unicast the response to the acquired address or
+ // broadcast it.
+ req->setFlags(Pkt4::FLAG_BROADCAST_MASK);
+ // This is a direct message, so the hops should be cleared.
+ req->setHops(0);
+ // Set local unicast address as if we are renewing a lease.
+ req->setLocalAddr(IOAddress("192.0.2.1"));
+ // Request is received on the DHCPv4 server port.
+ req->setLocalPort(DHCP4_SERVER_PORT);
+ // Set the interface. The response should be sent over the same interface.
+ req->setIface("eth1");
+ req->setIndex(ETH1_INDEX);
+
+ // Create the exchange using the req.
+ Dhcpv4Exchange ex = createExchange(req);
+ Pkt4Ptr resp = ex.getResponse();
+
+ // Let's extend the lease for the client in such a way that
+ // it will actually get different address. The response
+ // should not be sent to this address but rather to ciaddr
+ // as client still have ciaddr configured.
+ resp->setYiaddr(IOAddress("192.0.1.13"));
+ // Clear the remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+ // Copy hops value from the query.
+ resp->setHops(req->getHops());
+
+ ASSERT_NO_THROW(srv_.adjustIfaceData(ex));
+
+ // Check that server responds to ciaddr
+ EXPECT_EQ("192.0.1.15", resp->getRemoteAddr().toText());
+ // The query was non-relayed, so the response should be sent to a DHCPv4
+ // client port 68.
+ EXPECT_EQ(DHCP4_CLIENT_PORT, resp->getRemotePort());
+ // The response should be sent from the unicast address on which the
+ // query has been received.
+ EXPECT_EQ("192.0.2.1", resp->getLocalAddr().toText());
+ // The response should be sent from the DHCPv4 server port.
+ EXPECT_EQ(DHCP4_SERVER_PORT, resp->getLocalPort());
+ // The interface data should match the data in the query.
+ EXPECT_EQ("eth1", resp->getIface());
+ EXPECT_EQ(ETH1_INDEX, resp->getIndex());
+}
+
+// This test verifies that the destination address of the response message
+// is set to source address when the testing mode is enabled.
+// Renew: not testing mode was tested in adjustIfaceDataRenew.
+TEST_F(Dhcpv4SrvTest, adjustRemoteAddressRenewSendToSourceTestingModeEnabled) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Create instance of the incoming packet.
+ boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Clear giaddr to simulate direct packet.
+ req->setGiaddr(IOAddress("0.0.0.0"));
+ // Set ciaddr to non-zero address. The response should be sent to this
+ // address as the client is in renewing or rebinding state (it is fully
+ // configured).
+ req->setCiaddr(IOAddress("192.0.1.15"));
+ // Let's configure broadcast flag. It should be ignored because
+ // we are responding directly to the client having an address
+ // and trying to extend his lease. Broadcast flag is only used
+ // when new lease is acquired and server must make a decision
+ // whether to unicast the response to the acquired address or
+ // broadcast it.
+ req->setFlags(Pkt4::FLAG_BROADCAST_MASK);
+ // This is a direct message, so the hops should be cleared.
+ req->setHops(0);
+ // Set local unicast address as if we are renewing a lease.
+ req->setLocalAddr(IOAddress("192.0.2.1"));
+ // Request is received on the DHCPv4 server port.
+ req->setLocalPort(DHCP4_SERVER_PORT);
+ // Set the interface. The response should be sent over the same interface.
+ req->setIface("eth1");
+ req->setIndex(ETH1_INDEX);
+ // Set remote address.
+ req->setRemoteAddr(IOAddress("192.0.2.1"));
+
+ // Create the exchange using the req.
+ Dhcpv4Exchange ex = createExchange(req);
+ Pkt4Ptr resp = ex.getResponse();
+
+ // Let's extend the lease for the client in such a way that
+ // it will actually get different address. The response
+ // should not be sent to this address but rather to ciaddr
+ // as client still have ciaddr configured.
+ resp->setYiaddr(IOAddress("192.0.1.13"));
+ // Clear the remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+ // Copy hops value from the query.
+ resp->setHops(req->getHops());
+
+ // Set the testing mode.
+ srv_.setSendResponsesToSource(true);
+
+ ASSERT_NO_THROW(srv_.adjustIfaceData(ex));
+
+ // Check that server responds to source address.
+ EXPECT_EQ("192.0.2.1", resp->getRemoteAddr().toText());
+}
+
+// This test verifies that the destination address of the response message
+// is set correctly when giaddr and ciaddr is zeroed in the received message
+// and the new lease is acquired. The lease address is carried in the
+// response message in the yiaddr field. In this case destination address
+// of the response should be set to yiaddr if server supports direct responses
+// to the client which doesn't have an address yet or broadcast if the server
+// doesn't support direct responses.
+TEST_F(Dhcpv4SrvTest, adjustIfaceDataSelect) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Create instance of the incoming packet.
+ boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Clear giaddr to simulate direct packet.
+ req->setGiaddr(IOAddress("0.0.0.0"));
+ // Clear client address as it hasn't got any address configured yet.
+ req->setCiaddr(IOAddress("0.0.0.0"));
+
+ // Let's clear the broadcast flag.
+ req->setFlags(0);
+
+ // This is a non-relayed message, so let's clear hops count.
+ req->setHops(0);
+ // The query is sent to the broadcast address in the Select state.
+ req->setLocalAddr(IOAddress("255.255.255.255"));
+ // The query has been received on the DHCPv4 server port 67.
+ req->setLocalPort(DHCP4_SERVER_PORT);
+ // Set the interface. The response should be sent via the same interface.
+ req->setIface("eth1");
+ req->setIndex(ETH1_INDEX);
+
+ // Create the exchange using the req.
+ Dhcpv4Exchange ex = createExchange(req);
+ Pkt4Ptr resp = ex.getResponse();
+ // Assign some new address for this client.
+ resp->setYiaddr(IOAddress("192.0.1.13"));
+ // Clear the remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+ // Copy hops count.
+ resp->setHops(req->getHops());
+
+ // We want to test the case, when the server (packet filter) doesn't support
+ // direct responses to the client which doesn't have an address yet. In
+ // case, the server should send its response to the broadcast address.
+ // We can control whether the current packet filter returns that its support
+ // direct responses or not.
+ test_config.setDirectResponse(false);
+
+ // When running unit tests, the IfaceMgr is using the default Packet
+ // Filtering class, PktFilterInet. This class does not support direct
+ // responses to clients without address assigned. When giaddr and ciaddr
+ // are zero and client has just got new lease, the assigned address is
+ // carried in yiaddr. In order to send this address to the client,
+ // server must broadcast its response.
+ ASSERT_NO_THROW(srv_.adjustIfaceData(ex));
+
+ // Check that the response is sent to broadcast address as the
+ // server doesn't have capability to respond directly.
+ EXPECT_EQ("255.255.255.255", resp->getRemoteAddr().toText());
+
+ // Although the query has been sent to the broadcast address, the
+ // server should select a unicast address on the particular interface
+ // as a source address for the response.
+ EXPECT_EQ("192.0.2.3", resp->getLocalAddr().toText());
+
+ // The response should be sent from the DHCPv4 server port.
+ EXPECT_EQ(DHCP4_SERVER_PORT, resp->getLocalPort());
+
+ // The response should be sent via the same interface through which
+ // query has been received.
+ EXPECT_EQ("eth1", resp->getIface());
+ EXPECT_EQ(ETH1_INDEX, resp->getIndex());
+
+ // We also want to test the case when the server has capability to
+ // respond directly to the client which is not configured. Server
+ // makes decision whether it responds directly or broadcast its
+ // response based on the capability reported by IfaceMgr. We can
+ // control whether the current packet filter returns that it supports
+ // direct responses or not.
+ test_config.setDirectResponse(true);
+
+ // Now we expect that the server will send its response to the
+ // address assigned for the client.
+ ASSERT_NO_THROW(srv_.adjustIfaceData(ex));
+
+ EXPECT_EQ("192.0.1.13", resp->getRemoteAddr().toText());
+}
+
+// This test verifies that the destination address of the response message
+// is set to source address when the testing mode is enabled.
+// Select cases: not testing mode were tested in adjustIfaceDataSelect.
+TEST_F(Dhcpv4SrvTest, adjustRemoteAddressSelectSendToSourceTestingModeEnabled) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Create instance of the incoming packet.
+ boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Clear giaddr to simulate direct packet.
+ req->setGiaddr(IOAddress("0.0.0.0"));
+ // Clear client address as it hasn't got any address configured yet.
+ req->setCiaddr(IOAddress("0.0.0.0"));
+
+ // Let's clear the broadcast flag.
+ req->setFlags(0);
+
+ // This is a non-relayed message, so let's clear hops count.
+ req->setHops(0);
+ // The query is sent to the broadcast address in the Select state.
+ req->setLocalAddr(IOAddress("255.255.255.255"));
+ // The query has been received on the DHCPv4 server port 67.
+ req->setLocalPort(DHCP4_SERVER_PORT);
+ // Set the interface. The response should be sent via the same interface.
+ req->setIface("eth1");
+ req->setIndex(ETH1_INDEX);
+ // Set remote address.
+ req->setRemoteAddr(IOAddress("192.0.2.1"));
+
+ // Create the exchange using the req.
+ Dhcpv4Exchange ex = createExchange(req);
+ Pkt4Ptr resp = ex.getResponse();
+ // Assign some new address for this client.
+ resp->setYiaddr(IOAddress("192.0.1.13"));
+ // Clear the remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+ // Copy hops count.
+ resp->setHops(req->getHops());
+
+ // Disable direct responses.
+ test_config.setDirectResponse(false);
+
+ // Set the testing mode.
+ srv_.setSendResponsesToSource(true);
+
+ ASSERT_NO_THROW(srv_.adjustIfaceData(ex));
+
+ // Check that server responds to source address.
+ EXPECT_EQ("192.0.2.1", resp->getRemoteAddr().toText());
+
+ // Enable direct responses.
+ test_config.setDirectResponse(true);
+
+ // Clear the remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+
+ ASSERT_NO_THROW(srv_.adjustIfaceData(ex));
+
+ // Check that server still responds to source address.
+ EXPECT_EQ("192.0.2.1", resp->getRemoteAddr().toText());
+}
+
+// This test verifies that the destination address of the response message
+// is set to broadcast address when client set broadcast flag in its
+// query. Client sets this flag to indicate that it can't receive direct
+// responses from the server when it doesn't have its interface configured.
+// Server must respect broadcast flag.
+TEST_F(Dhcpv4SrvTest, adjustIfaceDataBroadcast) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Create instance of the incoming packet.
+ boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Clear giaddr to simulate direct packet.
+ req->setGiaddr(IOAddress("0.0.0.0"));
+ // Clear client address as it hasn't got any address configured yet.
+ req->setCiaddr(IOAddress("0.0.0.0"));
+ // The query is sent to the broadcast address in the Select state.
+ req->setLocalAddr(IOAddress("255.255.255.255"));
+ // The query has been received on the DHCPv4 server port 67.
+ req->setLocalPort(DHCP4_SERVER_PORT);
+ // Set the interface. The response should be sent via the same interface.
+ req->setIface("eth1");
+ req->setIndex(ETH1_INDEX);
+
+ // Let's set the broadcast flag.
+ req->setFlags(Pkt4::FLAG_BROADCAST_MASK);
+
+ // Create the exchange using the req.
+ Dhcpv4Exchange ex = createExchange(req);
+ Pkt4Ptr resp = ex.getResponse();
+
+ // Assign some new address for this client.
+ resp->setYiaddr(IOAddress("192.0.1.13"));
+
+ // Clear the remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+
+ ASSERT_NO_THROW(srv_.adjustIfaceData(ex));
+
+ // Server must respond to broadcast address when client desired that
+ // by setting the broadcast flag in its request.
+ EXPECT_EQ("255.255.255.255", resp->getRemoteAddr().toText());
+
+ // Although the query has been sent to the broadcast address, the
+ // server should select a unicast address on the particular interface
+ // as a source address for the response.
+ EXPECT_EQ("192.0.2.3", resp->getLocalAddr().toText());
+
+ // The response should be sent from the DHCPv4 server port.
+ EXPECT_EQ(DHCP4_SERVER_PORT, resp->getLocalPort());
+
+ // The response should be sent via the same interface through which
+ // query has been received.
+ EXPECT_EQ("eth1", resp->getIface());
+ EXPECT_EQ(ETH1_INDEX, resp->getIndex());
+}
+
+// This test verifies that the destination address of the response message
+// is set to source address when the testing mode is enabled.
+// Broadcast case: not testing mode was tested in adjustIfaceDataBroadcast.
+TEST_F(Dhcpv4SrvTest, adjustRemoteAddressBroadcastSendToSourceTestingModeEnabled) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Create instance of the incoming packet.
+ boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Clear giaddr to simulate direct packet.
+ req->setGiaddr(IOAddress("0.0.0.0"));
+ // Clear client address as it hasn't got any address configured yet.
+ req->setCiaddr(IOAddress("0.0.0.0"));
+ // The query is sent to the broadcast address in the Select state.
+ req->setLocalAddr(IOAddress("255.255.255.255"));
+ // The query has been received on the DHCPv4 server port 67.
+ req->setLocalPort(DHCP4_SERVER_PORT);
+ // Set the interface. The response should be sent via the same interface.
+ req->setIface("eth1");
+ req->setIndex(ETH1_INDEX);
+ // Set remote address.
+ req->setRemoteAddr(IOAddress("192.0.2.1"));
+
+ // Let's set the broadcast flag.
+ req->setFlags(Pkt4::FLAG_BROADCAST_MASK);
+
+ // Create the exchange using the req.
+ Dhcpv4Exchange ex = createExchange(req);
+ Pkt4Ptr resp = ex.getResponse();
+
+ // Assign some new address for this client.
+ resp->setYiaddr(IOAddress("192.0.1.13"));
+
+ // Clear the remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+
+ // Set the testing mode.
+ srv_.setSendResponsesToSource(true);
+
+ ASSERT_NO_THROW(srv_.adjustIfaceData(ex));
+
+ // Check that server responds to source address.
+ EXPECT_EQ("192.0.2.1", resp->getRemoteAddr().toText());
+}
+
+// This test verifies that the mandatory to copy fields and options
+// are really copied into the response.
+TEST_F(Dhcpv4SrvTest, initResponse) {
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Set fields which must be copied
+ query->setIface("foo");
+ query->setIndex(111);
+ query->setHops(5);
+ const HWAddr& hw = HWAddr::fromText("11:22:33:44:55:66:77:88", 10);
+ HWAddrPtr hw_addr(new HWAddr(hw));
+ query->setHWAddr(hw_addr);
+ query->setGiaddr(IOAddress("10.10.10.10"));
+ const HWAddr& src_hw = HWAddr::fromText("e4:ce:8f:12:34:56");
+ HWAddrPtr src_hw_addr(new HWAddr(src_hw));
+ query->setLocalHWAddr(src_hw_addr);
+ const HWAddr& dst_hw = HWAddr::fromText("e8:ab:cd:78:9a:bc");
+ HWAddrPtr dst_hw_addr(new HWAddr(dst_hw));
+ query->setRemoteHWAddr(dst_hw_addr);
+ query->setFlags(BOOTP_BROADCAST);
+
+ // Add options which must be copied
+ // client-id echo is optional
+ // rai echo is done in relayAgentInfoEcho
+ // Do subnet selection option
+ OptionDefinitionPtr sbnsel_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_SUBNET_SELECTION);
+ ASSERT_TRUE(sbnsel_def);
+ OptionCustomPtr sbnsel(new OptionCustom(*sbnsel_def, Option::V4));
+ ASSERT_TRUE(sbnsel);
+ sbnsel->writeAddress(IOAddress("192.0.2.3"));
+ query->addOption(sbnsel);
+
+ // Create exchange and get Response
+ Dhcpv4Exchange ex = createExchange(query);
+ Pkt4Ptr response = ex.getResponse();
+ ASSERT_TRUE(response);
+
+ // Check fields
+ EXPECT_EQ("foo", response->getIface());
+ EXPECT_EQ(111, response->getIndex());
+ EXPECT_TRUE(response->getSiaddr().isV4Zero());
+ EXPECT_TRUE(response->getCiaddr().isV4Zero());
+ EXPECT_EQ(5, response->getHops());
+ EXPECT_TRUE(hw == *response->getHWAddr());
+ EXPECT_EQ(IOAddress("10.10.10.10"), response->getGiaddr());
+ EXPECT_TRUE(src_hw == *response->getLocalHWAddr());
+ EXPECT_TRUE(dst_hw == *response->getRemoteHWAddr());
+ EXPECT_TRUE(BOOTP_BROADCAST == response->getFlags());
+
+ // Check options (i.e., subnet selection option)
+ OptionPtr resp_sbnsel = response->getOption(DHO_SUBNET_SELECTION);
+ ASSERT_TRUE(resp_sbnsel);
+ OptionCustomPtr resp_custom =
+ boost::dynamic_pointer_cast<OptionCustom>(resp_sbnsel);
+ ASSERT_TRUE(resp_custom);
+ IOAddress subnet_addr("0.0.0.0");
+ ASSERT_NO_THROW(subnet_addr = resp_custom->readAddress());
+ EXPECT_EQ(IOAddress("192.0.2.3"), subnet_addr);
+}
+
+// This test verifies that the server identifier option is appended to
+// a specified DHCPv4 message and the server identifier is correct.
+TEST_F(Dhcpv4SrvTest, appendServerID) {
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ Dhcpv4Exchange ex = createExchange(query);
+ Pkt4Ptr response = ex.getResponse();
+
+ // Set a local address. It is required by the function under test
+ // to create the Server Identifier option.
+ query->setLocalAddr(IOAddress("192.0.3.1"));
+
+ // Append the Server Identifier.
+ ASSERT_NO_THROW(NakedDhcpv4Srv::appendServerID(ex));
+
+ // Make sure that the option has been added.
+ OptionPtr opt = response->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+ ASSERT_TRUE(opt);
+ Option4AddrLstPtr opt_server_id =
+ boost::dynamic_pointer_cast<Option4AddrLst>(opt);
+ ASSERT_TRUE(opt_server_id);
+
+ // The option is represented as a list of IPv4 addresses but with
+ // only one address added.
+ Option4AddrLst::AddressContainer addrs = opt_server_id->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ // This address should match the local address of the packet.
+ EXPECT_EQ("192.0.3.1", addrs[0].toText());
+}
+
+// Sanity check. Verifies that both Dhcpv4Srv and its derived
+// class NakedDhcpv4Srv can be instantiated and destroyed.
+TEST_F(Dhcpv4SrvTest, basic) {
+
+ // Check that the base class can be instantiated
+ boost::scoped_ptr<Dhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new Dhcpv4Srv(DHCP4_SERVER_PORT + 10000, false,
+ false)));
+ srv.reset();
+ // We have to close open sockets because further in this test we will
+ // call the Dhcpv4Srv constructor again. This constructor will try to
+ // set the appropriate packet filter class for IfaceMgr. This requires
+ // that all sockets are closed.
+ IfaceMgr::instance().closeSockets();
+
+ // Check that the derived class can be instantiated
+ boost::scoped_ptr<NakedDhcpv4Srv> naked_srv;
+ ASSERT_NO_THROW(
+ naked_srv.reset(new NakedDhcpv4Srv(DHCP4_SERVER_PORT + 10000)));
+ // Close sockets again for the next test.
+ IfaceMgr::instance().closeSockets();
+
+ ASSERT_NO_THROW(naked_srv.reset(new NakedDhcpv4Srv(0)));
+}
+
+// This test verifies the test_send_responses_to_source_ is false by default
+// and sets by the KEA_TEST_SEND_RESPONSES_TO_SOURCE environment variable.
+TEST_F(Dhcpv4SrvTest, testSendResponsesToSource) {
+
+ ASSERT_FALSE(std::getenv("KEA_TEST_SEND_RESPONSES_TO_SOURCE"));
+ boost::scoped_ptr<NakedDhcpv4Srv> naked_srv;
+ ASSERT_NO_THROW(
+ naked_srv.reset(new NakedDhcpv4Srv(DHCP4_SERVER_PORT + 10000)));
+ EXPECT_FALSE(naked_srv->getSendResponsesToSource());
+ ::setenv("KEA_TEST_SEND_RESPONSES_TO_SOURCE", "ENABLED", 1);
+ // Do not use ASSERT as we want unsetenv to be always called.
+ EXPECT_NO_THROW(
+ naked_srv.reset(new NakedDhcpv4Srv(DHCP4_SERVER_PORT + 10000)));
+ EXPECT_TRUE(naked_srv->getSendResponsesToSource());
+ ::unsetenv("KEA_TEST_SEND_RESPONSES_TO_SOURCE");
+}
+
+// Verifies that DISCOVER message can be processed correctly,
+// that the OFFER message generated in response is valid and
+// contains necessary options.
+//
+// Note: this test focuses on the packet correctness. There
+// are other tests that verify correctness of the allocation
+// engine. See DiscoverBasic, DiscoverHint, DiscoverNoClientId
+// and DiscoverInvalidHint.
+TEST_F(Dhcpv4SrvTest, processDiscover) {
+ testDiscoverRequest(DHCPDISCOVER);
+}
+
+// Verifies that REQUEST message can be processed correctly,
+// that the OFFER message generated in response is valid and
+// contains necessary options.
+//
+// Note: this test focuses on the packet correctness. There
+// are other tests that verify correctness of the allocation
+// engine. See DiscoverBasic, DiscoverHint, DiscoverNoClientId
+// and DiscoverInvalidHint.
+TEST_F(Dhcpv4SrvTest, processRequest) {
+ testDiscoverRequest(DHCPREQUEST);
+}
+
+// Verifies that DHCPDISCOVERs are sanity checked correctly.
+// 1. They must have either hardware address or client id
+// 2. They must not have server id
+TEST_F(Dhcpv4SrvTest, sanityCheckDiscover) {
+ NakedDhcpv4Srv srv;
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Should throw, no hardware address or client id
+ ASSERT_THROW_MSG(srv.processDiscover(pkt), RFCViolation,
+ "Missing or useless client-id and no HW address"
+ " provided in message DHCPDISCOVER");
+
+ // Add a hardware address. This should not throw.
+ std::vector<uint8_t> data = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ HWAddrPtr hwaddr(new HWAddr(data, HTYPE_ETHER));
+ pkt->setHWAddr(hwaddr);
+ ASSERT_NO_THROW(srv.processDiscover(pkt));
+
+ // Now let's make a new pkt with client-id only, it should not throw.
+ pkt.reset(new Pkt4(DHCPDISCOVER, 1234));
+ pkt->addOption(generateClientId());
+ ASSERT_NO_THROW(srv.processDiscover(pkt));
+
+ // Now let's add a server-id. This should throw.
+ OptionDefinitionPtr server_id_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_SERVER_IDENTIFIER);
+ ASSERT_TRUE(server_id_def);
+
+ OptionCustomPtr server_id(new OptionCustom(*server_id_def, Option::V4));
+ server_id->writeAddress(IOAddress("192.0.2.3"));
+ pkt->addOption(server_id);
+ EXPECT_THROW_MSG(srv.processDiscover(pkt), RFCViolation,
+ "Server-id option was not expected,"
+ " but received in message DHCPDISCOVER");
+}
+
+// Verifies that DHCPREQEUSTs are sanity checked correctly.
+// 1. They must have either hardware address or client id
+// 2. They must have a requested address
+// 3. They may or may not have a server id
+TEST_F(Dhcpv4SrvTest, sanityCheckRequest) {
+ NakedDhcpv4Srv srv;
+ Pkt4Ptr pkt(new Pkt4(DHCPREQUEST, 1234));
+
+ // Should throw, no hardware address or client id
+ ASSERT_THROW_MSG(srv.processRequest(pkt), RFCViolation,
+ "Missing or useless client-id and no HW address"
+ " provided in message DHCPREQUEST");
+
+ // Add a hardware address. Should not throw.
+ std::vector<uint8_t> data = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ HWAddrPtr hwaddr(new HWAddr(data, HTYPE_ETHER));
+ pkt->setHWAddr(hwaddr);
+ EXPECT_NO_THROW(srv.processRequest(pkt));
+
+ // Now let's add a requested address. This should not throw.
+ OptionDefinitionPtr req_addr_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_REQUESTED_ADDRESS);
+ ASSERT_TRUE(req_addr_def);
+ OptionCustomPtr req_addr(new OptionCustom(*req_addr_def, Option::V4));
+ req_addr->writeAddress(IOAddress("192.0.2.3"));
+ pkt->addOption(req_addr);
+ ASSERT_NO_THROW(srv.processRequest(pkt));
+
+ // Now let's make a new pkt with client-id only and an address, it should not throw.
+ pkt.reset(new Pkt4(DHCPREQUEST, 1234));
+ pkt->addOption(generateClientId());
+ pkt->addOption(req_addr);
+ ASSERT_NO_THROW(srv.processRequest(pkt));
+
+ // Now let's add a server-id. This should not throw.
+ OptionDefinitionPtr server_id_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_SERVER_IDENTIFIER);
+ ASSERT_TRUE(server_id_def);
+
+ OptionCustomPtr server_id(new OptionCustom(*server_id_def, Option::V4));
+ server_id->writeAddress(IOAddress("192.0.2.3"));
+ pkt->addOption(server_id);
+ EXPECT_NO_THROW(srv.processRequest(pkt));
+}
+
+// Verifies that DHCPDECLINEs are sanity checked correctly.
+// 1. They must have either hardware address or client id
+// 2. They must have a requested address
+// 3. They may or may not have a server id
+TEST_F(Dhcpv4SrvTest, sanityCheckDecline) {
+ NakedDhcpv4Srv srv;
+ Pkt4Ptr pkt(new Pkt4(DHCPDECLINE, 1234));
+
+ // Should throw, no hardware address or client id
+ ASSERT_THROW_MSG(srv.processDecline(pkt), RFCViolation,
+ "Missing or useless client-id and no HW address"
+ " provided in message DHCPDECLINE");
+
+ // Add a hardware address. Should throw because of missing address.
+ std::vector<uint8_t> data = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ HWAddrPtr hwaddr(new HWAddr(data, HTYPE_ETHER));
+ pkt->setHWAddr(hwaddr);
+ ASSERT_THROW_MSG(srv.processDecline(pkt), RFCViolation,
+ "Mandatory 'Requested IP address' option missing in DHCPDECLINE"
+ " sent from [hwtype=1 00:fe:fe:fe:fe:fe], cid=[no info], tid=0x4d2");
+
+ // Now let's add a requested address. This should not throw.
+ OptionDefinitionPtr req_addr_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_REQUESTED_ADDRESS);
+ ASSERT_TRUE(req_addr_def);
+ OptionCustomPtr req_addr(new OptionCustom(*req_addr_def, Option::V4));
+ req_addr->writeAddress(IOAddress("192.0.2.3"));
+ pkt->addOption(req_addr);
+ ASSERT_NO_THROW(srv.processDecline(pkt));
+
+ // Now let's make a new pkt with client-id only and an address, it should not throw.
+ pkt.reset(new Pkt4(DHCPDECLINE, 1234));
+ pkt->addOption(generateClientId());
+ pkt->addOption(req_addr);
+ ASSERT_NO_THROW(srv.processDecline(pkt));
+
+ // Now let's add a server-id. This should not throw.
+ OptionDefinitionPtr server_id_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_SERVER_IDENTIFIER);
+ ASSERT_TRUE(server_id_def);
+
+ OptionCustomPtr server_id(new OptionCustom(*server_id_def, Option::V4));
+ server_id->writeAddress(IOAddress("192.0.2.3"));
+ pkt->addOption(server_id);
+ EXPECT_NO_THROW(srv.processDecline(pkt));
+}
+
+// Verifies that DHCPRELEASEs are sanity checked correctly.
+// 1. They must have either hardware address or client id
+// 2. They may or may not have a server id
+TEST_F(Dhcpv4SrvTest, sanityCheckRelease) {
+ NakedDhcpv4Srv srv;
+ Pkt4Ptr pkt(new Pkt4(DHCPRELEASE, 1234));
+
+ // Should throw, no hardware address or client id
+ ASSERT_THROW_MSG(srv.processRelease(pkt), RFCViolation,
+ "Missing or useless client-id and no HW address"
+ " provided in message DHCPRELEASE");
+
+ // Add a hardware address. Should not throw.
+ std::vector<uint8_t> data = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ HWAddrPtr hwaddr(new HWAddr(data, HTYPE_ETHER));
+ pkt->setHWAddr(hwaddr);
+ EXPECT_NO_THROW(srv.processRelease(pkt));
+
+ // Make a new pkt with client-id only. Should not throw.
+ pkt.reset(new Pkt4(DHCPRELEASE, 1234));
+ pkt->addOption(generateClientId());
+ ASSERT_NO_THROW(srv.processRelease(pkt));
+
+ // Now let's add a server-id. This should not throw.
+ OptionDefinitionPtr server_id_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_SERVER_IDENTIFIER);
+ ASSERT_TRUE(server_id_def);
+
+ OptionCustomPtr server_id(new OptionCustom(*server_id_def, Option::V4));
+ server_id->writeAddress(IOAddress("192.0.2.3"));
+ pkt->addOption(server_id);
+ EXPECT_NO_THROW(srv.processRelease(pkt));
+}
+
+// Verifies that DHCPINFORMs are sanity checked correctly.
+// 1. They must have either hardware address or client id
+// 2. They may or may not have requested address
+// 3. They may or may not have a server id
+TEST_F(Dhcpv4SrvTest, sanityCheckInform) {
+ NakedDhcpv4Srv srv;
+ Pkt4Ptr pkt(new Pkt4(DHCPINFORM, 1234));
+
+ // Should throw, no hardware address or client id
+ ASSERT_THROW_MSG(srv.processInform(pkt), RFCViolation,
+ "Missing or useless client-id and no HW address"
+ " provided in message DHCPINFORM");
+
+ // Add a hardware address. Should not throw.
+ std::vector<uint8_t> data = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ HWAddrPtr hwaddr(new HWAddr(data, HTYPE_ETHER));
+ pkt->setHWAddr(hwaddr);
+ ASSERT_NO_THROW(srv.processInform(pkt));
+
+ // Now let's add a requested address. This should not throw.
+ OptionDefinitionPtr req_addr_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_REQUESTED_ADDRESS);
+ ASSERT_TRUE(req_addr_def);
+ OptionCustomPtr req_addr(new OptionCustom(*req_addr_def, Option::V4));
+ req_addr->writeAddress(IOAddress("192.0.2.3"));
+ pkt->addOption(req_addr);
+ ASSERT_NO_THROW(srv.processInform(pkt));
+
+ // Now let's make a new pkt with client-id only and an address, it should not throw.
+ pkt.reset(new Pkt4(DHCPINFORM, 1234));
+ pkt->addOption(generateClientId());
+ pkt->addOption(req_addr);
+ ASSERT_NO_THROW(srv.processInform(pkt));
+
+ // Now let's add a server-id. This should not throw.
+ OptionDefinitionPtr server_id_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_SERVER_IDENTIFIER);
+ ASSERT_TRUE(server_id_def);
+
+ OptionCustomPtr server_id(new OptionCustom(*server_id_def, Option::V4));
+ server_id->writeAddress(IOAddress("192.0.2.3"));
+ pkt->addOption(server_id);
+ EXPECT_NO_THROW(srv.processInform(pkt));
+}
+
+// This test verifies that incoming DISCOVER can be handled properly, that an
+// OFFER is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed very simple DISCOVER message with:
+// - client-id option
+//
+// expected returned OFFER message:
+// - copy of client-id
+// - server-id
+// - offered address
+TEST_F(Dhcpv4SrvTest, DiscoverBasic) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+ dis->setIface("eth1");
+ dis->setIndex(ETH1_INDEX);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv->processDiscover(dis);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(offer, subnet_, true, true);
+
+ // Check identifiers
+ checkServerId(offer, srv->getServerID());
+ checkClientId(offer, clientid);
+}
+
+// This test verifies that OFFERs return expected valid lifetimes.
+TEST_F(Dhcpv4SrvTest, DiscoverValidLifetime) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ // Recreate subnet
+ Triplet<uint32_t> unspecified;
+ Triplet<uint32_t> valid_lft(500, 1000, 1500);
+ subnet_.reset(new Subnet4(IOAddress("192.0.2.0"), 24,
+ unspecified,
+ unspecified,
+ valid_lft));
+
+ pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"),
+ IOAddress("192.0.2.110")));
+ subnet_->addPool(pool_);
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet_);
+ CfgMgr::instance().commit();
+
+ // Struct for describing an individual lifetime test scenario
+ struct LifetimeTest {
+ // logged test description
+ std::string description_;
+ // lifetime hint (0 means not send dhcp-lease-time option)
+ uint32_t hint;
+ // expected returned value
+ uint32_t expected;
+ };
+
+ // Test scenarios
+ std::vector<LifetimeTest> tests = {
+ { "default valid lifetime", 0, 1000 },
+ { "specified valid lifetime", 1001, 1001 },
+ { "too small valid lifetime", 100, 500 },
+ { "too large valid lifetime", 2000, 1500 }
+ };
+
+ // Iterate over the test scenarios.
+ for (auto test : tests) {
+ SCOPED_TRACE(test.description_);
+
+ // Create a discover packet to use
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+ dis->setIface("eth1");
+ dis->setIndex(ETH1_INDEX);
+
+ // Add dhcp-lease-time option.
+ if (test.hint) {
+ OptionUint32Ptr opt(new OptionUint32(Option::V4,
+ DHO_DHCP_LEASE_TIME,
+ test.hint));
+ dis->addOption(opt);
+ }
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv->processDiscover(dis);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct and has the expected value.
+ checkAddressParams(offer, subnet_, false, false, test.expected);
+
+ // Check identifiers
+ checkServerId(offer, srv->getServerID());
+ checkClientId(offer, clientid);
+ }
+}
+
+// Check that option 58 and 59 are only included if they were specified
+// (and calculate-tee-times = false) and the values are sane:
+// T2 is less than valid lft; T1 is less than T2 (if given) or valid
+// lft if T2 is not given.
+TEST_F(Dhcpv4SrvTest, DiscoverTimers) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ // Recreate subnet
+ Triplet<uint32_t> unspecified;
+ Triplet<uint32_t> valid_lft(1000);
+ subnet_.reset(new Subnet4(IOAddress("192.0.2.0"), 24,
+ unspecified,
+ unspecified,
+ valid_lft));
+
+ pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"),
+ IOAddress("192.0.2.110")));
+ subnet_->addPool(pool_);
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet_);
+ CfgMgr::instance().commit();
+
+ // 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_;
+ // True if Offer should contain Subnet's T1 value
+ bool exp_t1_;
+ // True if Offer should contain Subnet's T2 value
+ bool exp_t2_;
+ };
+
+ // Convenience constants
+ bool T1 = true;
+ bool T2 = true;
+
+ // Test scenarios
+ std::vector<TimerTest> tests = {
+ {
+ "T1:unspecified, T2:unspecified",
+ unspecified, unspecified,
+ // Client should neither.
+ !T1, !T2
+ },
+ {
+ "T1 unspecified, T2 < VALID",
+ unspecified, valid_lft - 1,
+ // Client should only get T2.
+ !T1, T2
+ },
+ {
+ "T1:unspecified, T2 = VALID",
+ unspecified, valid_lft,
+ // Client should get neither.
+ !T1, !T2
+ },
+ {
+ "T1:unspecified, T2 > VALID",
+ unspecified, valid_lft + 1,
+ // Client should get neither.
+ !T1, !T2
+ },
+
+ {
+ "T1 < VALID, T2:unspecified",
+ valid_lft - 1, unspecified,
+ // Client should only get T1.
+ T1, !T2
+ },
+ {
+ "T1 = VALID, T2:unspecified",
+ valid_lft, unspecified,
+ // Client should get neither.
+ !T1, !T2
+ },
+ {
+ "T1 > VALID, T2:unspecified",
+ valid_lft + 1, unspecified,
+ // Client should get neither.
+ !T1, !T2
+ },
+ {
+ "T1 < T2 < VALID",
+ valid_lft - 2, valid_lft - 1,
+ // Client should get both.
+ T1, T2
+ },
+ {
+ "T1 = T2 < VALID",
+ valid_lft - 1, valid_lft - 1,
+ // Client should only get T2.
+ !T1, T2
+ },
+ {
+ "T1 > T2 < VALID",
+ valid_lft - 1, valid_lft - 2,
+ // Client should only get T2.
+ !T1, T2
+ },
+ {
+ "T1 = T2 = VALID",
+ valid_lft, valid_lft,
+ // Client should get neither.
+ !T1, !T2
+ },
+ {
+ "T1 > VALID < T2, T2 > VALID",
+ valid_lft + 1, valid_lft + 2,
+ // Client should get neither.
+ !T1, !T2
+ }
+ };
+
+ // Create a discover packet to use
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+ dis->setIface("eth1");
+ dis->setIndex(ETH1_INDEX);
+
+ // Iterate over the test scenarios.
+ for (auto test = tests.begin(); test != tests.end(); ++test) {
+ {
+ SCOPED_TRACE((*test).description_);
+ // Configure subnet's timer values
+ subnet_->setT1((*test).cfg_t1_);
+ subnet_->setT2((*test).cfg_t2_);
+
+ // Discover/Offer exchange with the server
+ Pkt4Ptr offer = srv->processDiscover(dis);
+
+ // Verify we have an offer
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Verify the timers are as expected.
+ checkAddressParams(offer, subnet_,
+ (*test).exp_t1_, (*test).exp_t2_);
+ }
+ }
+}
+
+// Check that option 58 and 59 are included when calculate-tee-times
+// is enabled, but only when they are not explicitly specified via
+// renew-timer and rebinding-timer. This test does not check whether
+// the subnet's for t1-percent and t2-percent are valid, as this is
+// enforced by parsing and tested elsewhere.
+TEST_F(Dhcpv4SrvTest, calculateTeeTimers) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ // Recreate subnet
+ Triplet<uint32_t> unspecified;
+ Triplet<uint32_t> valid_lft(1000);
+ subnet_.reset(new Subnet4(IOAddress("192.0.2.0"), 24,
+ unspecified,
+ unspecified,
+ valid_lft));
+
+ pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"),
+ IOAddress("192.0.2.110")));
+ subnet_->addPool(pool_);
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet_);
+ CfgMgr::instance().commit();
+
+ // 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_;
+ // 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_;
+ };
+
+ // Convenience constant
+ uint32_t not_expected = 0;
+
+ // Test scenarios
+ std::vector<TimerTest> tests = {
+ {
+ "T1 and T2 calculated",
+ unspecified, unspecified,
+ 0.4, 0.8,
+ 400, 800
+ },
+ {
+ "T1 and T2 specified insane",
+ valid_lft + 1, valid_lft + 2,
+ 0.4, 0.8,
+ not_expected, not_expected
+ },
+ {
+ "T1 should be calculated, T2 specified",
+ unspecified, valid_lft - 1,
+ 0.4, 0.8,
+ 400, valid_lft - 1
+ },
+ {
+ "T1 specified, T2 should be calculated",
+ 299, unspecified,
+ 0.4, 0.8,
+ 299, 800
+ },
+ {
+ "T1 specified > T2, T2 should be calculated",
+ valid_lft - 1, unspecified,
+ 0.4, 0.8,
+ not_expected, 800
+ }
+ };
+
+ // Calculation is enabled for all the scenarios.
+ subnet_->setCalculateTeeTimes(true);
+
+ // Create a discover packet to use
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+ dis->setIface("eth1");
+ dis->setIndex(ETH1_INDEX);
+
+ // Iterate over the test scenarios.
+ for (auto test = tests.begin(); test != tests.end(); ++test) {
+ {
+ SCOPED_TRACE((*test).description_);
+ // Configure subnet's timer values
+ subnet_->setT1((*test).cfg_t1_);
+ subnet_->setT2((*test).cfg_t2_);
+
+ subnet_->setT1Percent((*test).t1_percent_);
+ subnet_->setT2Percent((*test).t2_percent_);
+
+ // Discover/Offer exchange with the server
+ Pkt4Ptr offer = srv->processDiscover(dis);
+
+ // Verify we have an offer
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Check T1 timer
+ OptionUint32Ptr opt = boost::dynamic_pointer_cast
+ <OptionUint32> (offer->getOption(DHO_DHCP_RENEWAL_TIME));
+
+ if ((*test).t1_exp_value_ == not_expected) {
+ EXPECT_FALSE(opt) << "T1 present and shouldn't be";
+ } else {
+ ASSERT_TRUE(opt) << "Required T1 option missing or it has"
+ " an unexpected type";
+ EXPECT_EQ(opt->getValue(), (*test).t1_exp_value_);
+ }
+
+ // Check T2 timer
+ opt = boost::dynamic_pointer_cast
+ <OptionUint32>(offer->getOption(DHO_DHCP_REBINDING_TIME));
+
+ if ((*test).t2_exp_value_ == not_expected) {
+ EXPECT_FALSE(opt) << "T2 present and shouldn't be";
+ } else {
+ ASSERT_TRUE(opt) << "Required T2 option missing or it has"
+ " an unexpected type";
+ EXPECT_EQ(opt->getValue(), (*test).t2_exp_value_);
+ }
+ }
+ }
+}
+
+// This test verifies that incoming DISCOVER can be handled properly, that an
+// OFFER is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed very simple DISCOVER message with:
+// - client-id option
+// - address set to specific value as hint, but that hint is invalid
+//
+// expected returned OFFER message:
+// - copy of client-id
+// - server-id
+// - offered address (!= hint)
+TEST_F(Dhcpv4SrvTest, DiscoverInvalidHint) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+ IOAddress hint("10.1.2.3");
+
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.107"));
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+ dis->setYiaddr(hint);
+ dis->setIface("eth1");
+ dis->setIndex(ETH1_INDEX);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv->processDiscover(dis);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(offer, subnet_, true, true);
+
+ EXPECT_NE(offer->getYiaddr(), hint);
+
+ // Check identifiers
+ checkServerId(offer, srv->getServerID());
+ checkClientId(offer, 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 OFFERs. Please note that OFFER 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 offer as the first one
+// and this is a correct behavior. It is REQUEST that will fail for the third
+// client. OFFER is basically saying "if you send me a request, you will
+// probably get an address like this" (there are no guarantees).
+TEST_F(Dhcpv4SrvTest, ManyDiscovers) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ Pkt4Ptr dis1 = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr dis2 = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 2345));
+ Pkt4Ptr dis3 = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 3456));
+
+ dis1->setRemoteAddr(IOAddress("192.0.2.1"));
+ dis2->setRemoteAddr(IOAddress("192.0.2.2"));
+ dis3->setRemoteAddr(IOAddress("192.0.2.3"));
+
+ // Assign interfaces
+ dis1->setIface("eth1");
+ dis1->setIndex(ETH1_INDEX);
+ dis2->setIface("eth1");
+ dis2->setIndex(ETH1_INDEX);
+ dis3->setIface("eth1");
+ dis3->setIndex(ETH1_INDEX);
+
+ // Different client-id sizes
+ OptionPtr clientid1 = generateClientId(4); // length 4
+ OptionPtr clientid2 = generateClientId(5); // length 5
+ OptionPtr clientid3 = generateClientId(6); // length 6
+
+ dis1->addOption(clientid1);
+ dis2->addOption(clientid2);
+ dis3->addOption(clientid3);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer1 = srv->processDiscover(dis1);
+ Pkt4Ptr offer2 = srv->processDiscover(dis2);
+ Pkt4Ptr offer3 = srv->processDiscover(dis3);
+
+ // Check if we get response at all
+ checkResponse(offer1, DHCPOFFER, 1234);
+ checkResponse(offer2, DHCPOFFER, 2345);
+ checkResponse(offer3, DHCPOFFER, 3456);
+
+ IOAddress addr1 = offer1->getYiaddr();
+ IOAddress addr2 = offer2->getYiaddr();
+ IOAddress addr3 = offer3->getYiaddr();
+
+ // Check that the assigned address is indeed from the configured pool
+ checkAddressParams(offer1, subnet_, true, true);
+ checkAddressParams(offer2, subnet_, true, true);
+ checkAddressParams(offer3, subnet_, true, true);
+
+ // Check server-ids
+ checkServerId(offer1, srv->getServerID());
+ checkServerId(offer2, srv->getServerID());
+ checkServerId(offer3, srv->getServerID());
+ checkClientId(offer1, clientid1);
+ checkClientId(offer2, clientid2);
+ checkClientId(offer3, clientid3);
+
+ // Finally check that the addresses offered are different
+ EXPECT_NE(addr1, addr2);
+ EXPECT_NE(addr2, addr3);
+ EXPECT_NE(addr3, addr1);
+ cout << "Offered address to client1=" << addr1 << endl;
+ cout << "Offered address to client2=" << addr2 << endl;
+ cout << "Offered address to client3=" << addr3 << endl;
+}
+
+// Checks whether echoing back client-id is controllable, i.e.
+// whether the server obeys echo-client-id and sends (or not)
+// client-id
+TEST_F(Dhcpv4SrvTest, discoverEchoClientId) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ NakedDhcpv4Srv srv(0);
+
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+ dis->setIface("eth1");
+ dis->setIndex(ETH1_INDEX);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv.processDiscover(dis);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+ checkClientId(offer, clientid);
+
+ ConstSrvConfigPtr cfg = CfgMgr::instance().getCurrentCfg();
+ const Subnet4Collection* subnets = cfg->getCfgSubnets4()->getAll();
+ ASSERT_EQ(1, subnets->size());
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(*subnets->begin());
+ CfgMgr::instance().getStagingCfg()->setEchoClientId(false);
+ CfgMgr::instance().commit();
+
+ offer = srv.processDiscover(dis);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+ checkClientId(offer, clientid);
+}
+
+// This test verifies that incoming DISCOVER can reuse an existing lease.
+TEST_F(Dhcpv4SrvTest, DiscoverCache) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ // Enable lease reuse.
+ subnet_->setCacheThreshold(.1);
+
+ const IOAddress addr("192.0.2.106");
+ const uint32_t temp_valid = subnet_->getValid();
+ const int delta = 100;
+ const time_t temp_timestamp = time(NULL) - delta;
+
+ // Generate client-id also sets client_id_ member
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_V4, addr));
+
+ // let's create a lease and put it in the LeaseMgr
+ uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER));
+ Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2,
+ &client_id_->getDuid()[0], client_id_->getDuid().size(),
+ temp_valid, temp_timestamp, subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database
+ Lease4Ptr l = LeaseMgrFactory::instance().getLease4(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->valid_lft_, temp_valid);
+ EXPECT_EQ(l->cltt_, temp_timestamp);
+
+ // Let's create a DISCOVER
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress(addr));
+ dis->addOption(clientid);
+ dis->setIface("eth0");
+ dis->setIndex(ETH0_INDEX);
+ dis->setHWAddr(hwaddr2);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv->processDiscover(dis);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Check valid lifetime (temp_valid - age)
+ OptionUint32Ptr opt = boost::dynamic_pointer_cast<
+ OptionUint32>(offer->getOption(DHO_DHCP_LEASE_TIME));
+ ASSERT_TRUE(opt);
+ EXPECT_GE(subnet_->getValid() - delta, opt->getValue());
+ EXPECT_LE(subnet_->getValid() - delta - 10, opt->getValue());
+
+ // Check address
+ EXPECT_EQ(addr, offer->getYiaddr());
+
+ // Check T1
+ opt = boost::dynamic_pointer_cast<
+ OptionUint32>(offer->getOption(DHO_DHCP_RENEWAL_TIME));
+ ASSERT_TRUE(opt);
+ EXPECT_EQ(opt->getValue(), subnet_->getT1());
+
+ // Check T2
+ opt = boost::dynamic_pointer_cast<
+ OptionUint32>(offer->getOption(DHO_DHCP_REBINDING_TIME));
+ ASSERT_TRUE(opt);
+ EXPECT_EQ(opt->getValue(), subnet_->getT2());
+
+ // Check identifiers
+ checkServerId(offer, srv->getServerID());
+ checkClientId(offer, clientid);
+}
+
+// Check that option 58 and 59 are not included if they are not specified.
+TEST_F(Dhcpv4SrvTest, RequestNoTimers) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+ req->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ req->addOption(clientid);
+ req->setIface("eth1");
+ req->setIndex(ETH1_INDEX);
+
+ // Recreate a subnet but set T1 and T2 to "unspecified".
+ subnet_.reset(new Subnet4(IOAddress("192.0.2.0"), 24,
+ Triplet<uint32_t>(),
+ Triplet<uint32_t>(),
+ 3000));
+ pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"),
+ IOAddress("192.0.2.110")));
+ subnet_->addPool(pool_);
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet_);
+ CfgMgr::instance().commit();
+
+ // Pass it to the server and get an ACK.
+ Pkt4Ptr ack = srv->processRequest(req);
+
+ // Check if we get response at all
+ checkResponse(ack, DHCPACK, 1234);
+
+ // T1 and T2 timers must not be present.
+ checkAddressParams(ack, subnet_, false, false);
+
+ // Check identifiers
+ checkServerId(ack, srv->getServerID());
+ checkClientId(ack, clientid);
+}
+
+// Checks whether echoing back client-id is controllable
+TEST_F(Dhcpv4SrvTest, requestEchoClientId) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ NakedDhcpv4Srv srv(0);
+
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+ dis->setIface("eth1");
+ dis->setIndex(ETH1_INDEX);
+
+ // Pass it to the server and get ACK
+ Pkt4Ptr ack = srv.processRequest(dis);
+
+ // Check if we get response at all
+ checkResponse(ack, DHCPACK, 1234);
+ checkClientId(ack, clientid);
+
+ ConstSrvConfigPtr cfg = CfgMgr::instance().getCurrentCfg();
+ const Subnet4Collection* subnets = cfg->getCfgSubnets4()->getAll();
+ ASSERT_EQ(1, subnets->size());
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(*subnets->begin());
+ CfgMgr::instance().getStagingCfg()->setEchoClientId(false);
+ CfgMgr::instance().commit();
+
+ ack = srv.processRequest(dis);
+
+ // Check if we get response at all
+ checkResponse(ack, DHCPACK, 1234);
+ checkClientId(ack, clientid);
+}
+
+// This test verifies that incoming (positive) REQUEST/Renewing 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 that includes IAADDR
+// - lease is actually renewed in LeaseMgr
+TEST_F(Dhcpv4SrvTest, RenewBasic) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ const IOAddress addr("192.0.2.106");
+ const uint32_t temp_valid = 100;
+ const time_t temp_timestamp = time(NULL) - 10;
+
+ // Generate client-id also sets client_id_ member
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_V4, addr));
+
+ // let's create a lease and put it in the LeaseMgr
+ uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER));
+ Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2,
+ &client_id_->getDuid()[0], client_id_->getDuid().size(),
+ temp_valid, temp_timestamp, subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database
+ Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // Check that preferred, valid and cltt really set.
+ // Constructed lease looks as if it was assigned 10 seconds ago
+ EXPECT_EQ(l->valid_lft_, temp_valid);
+ EXPECT_EQ(l->cltt_, temp_timestamp);
+
+ // Let's create a RENEW
+ Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+ req->setRemoteAddr(IOAddress(addr));
+ req->setYiaddr(addr);
+ req->setCiaddr(addr); // client's address
+ req->setIface("eth0");
+ req->setIndex(ETH0_INDEX);
+ req->setHWAddr(hwaddr2);
+
+ req->addOption(clientid);
+ req->addOption(srv->getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt4Ptr ack = srv->processRequest(req);
+
+ // Check if we get response at all
+ checkResponse(ack, DHCPACK, 1234);
+ EXPECT_EQ(addr, ack->getYiaddr());
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(ack, subnet_, true, true);
+
+ // Check identifiers
+ checkServerId(ack, srv->getServerID());
+ checkClientId(ack, clientid);
+
+ // Check that the lease is really in the database
+ l = checkLease(ack, clientid, req->getHWAddr(), addr);
+ ASSERT_TRUE(l);
+
+ // Check that preferred, valid and cltt were really updated
+ EXPECT_EQ(l->valid_lft_, subnet_->getValid());
+
+ // 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));
+
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(addr);
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(lease));
+}
+
+// Renew*Lifetime common code.
+namespace {
+
+struct ctx {
+ Dhcpv4SrvTest* test;
+ NakedDhcpv4Srv* srv;
+ const IOAddress& addr;
+ const uint32_t temp_valid;
+ const time_t temp_timestamp;
+ OptionPtr clientid;
+ HWAddrPtr hwaddr;
+ Lease4Ptr used;
+ Lease4Ptr l;
+ OptionPtr opt;
+ Pkt4Ptr req;
+ Pkt4Ptr ack;
+};
+
+void prepare(struct ctx& c) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(c.test->subnet_->inPool(Lease::TYPE_V4, c.addr));
+
+ // let's create a lease and put it in the LeaseMgr
+ uint8_t hwaddr_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ c.hwaddr.reset(new HWAddr(hwaddr_data, sizeof(hwaddr_data), HTYPE_ETHER));
+
+ c.used.reset(new Lease4(c.addr, c.hwaddr,
+ &c.test->client_id_->getDuid()[0],
+ c.test->client_id_->getDuid().size(),
+ c.temp_valid, c.temp_timestamp,
+ c.test->subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(c.used));
+
+ // Check that the lease is really in the database
+ c.l = LeaseMgrFactory::instance().getLease4(c.addr);
+ ASSERT_TRUE(c.l);
+
+ // Check that valid and cltt really set.
+ // Constructed lease looks as if it was assigned 10 seconds ago
+ EXPECT_EQ(c.l->valid_lft_, c.temp_valid);
+ EXPECT_EQ(c.l->cltt_, c.temp_timestamp);
+
+ // Set the valid lifetime interval.
+ c.test->subnet_->setValid(Triplet<uint32_t>(2000, 3000, 4000));
+
+ // Let's create a RENEW
+ c.req.reset(new Pkt4(DHCPREQUEST, 1234));
+ c.req->setRemoteAddr(IOAddress(c.addr));
+ c.req->setYiaddr(c.addr);
+ c.req->setCiaddr(c.addr); // client's address
+ c.req->setIface("eth0");
+ c.req->setIndex(ETH0_INDEX);
+ c.req->setHWAddr(c.hwaddr);
+
+ c.req->addOption(c.clientid);
+ c.req->addOption(c.srv->getServerID());
+
+ if (c.opt) {
+ c.req->addOption(c.opt);
+ }
+
+ // Pass it to the server and hope for a REPLY
+ c.ack = c.srv->processRequest(c.req);
+
+ // Check if we get response at all
+ c.test->checkResponse(c.ack, DHCPACK, 1234);
+ EXPECT_EQ(c.addr, c.ack->getYiaddr());
+
+ // Check identifiers
+ c.test->checkServerId(c.ack, c.srv->getServerID());
+ c.test->checkClientId(c.ack, c.clientid);
+
+ // Check that the lease is really in the database
+ c.l = c.test->checkLease(c.ack, c.clientid, c.req->getHWAddr(), c.addr);
+ ASSERT_TRUE(c.l);
+}
+
+// This test verifies that renewal returns the default valid lifetime
+// when the client does not specify a value.
+TEST_F(Dhcpv4SrvTest, RenewDefaultLifetime) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ struct ctx c = {
+ this, // test
+ srv.get(), // srv
+ IOAddress("192.0.2.106"), // addr
+ 100, // temp_valid
+ time(NULL) - 10, // temp_timestamp
+ // Generate client-id also sets client_id_ member
+ generateClientId(), // clientid
+ HWAddrPtr(), // hwaddr
+ Lease4Ptr(), // used
+ Lease4Ptr(), // l
+ OptionPtr(), // opt
+ Pkt4Ptr(), // req
+ Pkt4Ptr() // acka
+ };
+
+ prepare(c);
+
+ // There is no valid lifetime hint so the default will be returned.
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(c.ack, subnet_, true, true, subnet_->getValid());
+
+ // Check that valid and cltt were really updated
+ EXPECT_EQ(c.l->valid_lft_, subnet_->getValid());
+
+ // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors
+ int32_t cltt = static_cast<int32_t>(c.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));
+
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(c.addr);
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(lease));
+}
+
+// This test verifies that renewal returns the specified valid lifetime
+// when the client adds an in-bound hint in the DISCOVER.
+TEST_F(Dhcpv4SrvTest, RenewHintLifetime) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ struct ctx c = {
+ this, // test
+ srv.get(), // srv
+ IOAddress("192.0.2.106"), // addr
+ 100, // temp_valid
+ time(NULL) - 10, // temp_timestamp
+ // Generate client-id also sets client_id_ member
+ generateClientId(), // clientid
+ HWAddrPtr(), // hwaddr
+ Lease4Ptr(), // used
+ Lease4Ptr(), // l
+ OptionPtr(), // opt
+ Pkt4Ptr(), // req
+ Pkt4Ptr() // acka
+ };
+
+ // Add a dhcp-lease-time with an in-bound valid lifetime hint
+ // which will be returned in the OFFER.
+ uint32_t hint = 3001;
+ c.opt.reset(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME, hint));
+
+ prepare(c);
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(c.ack, subnet_, true, true, hint);
+
+ // Check that valid and cltt were really updated
+ EXPECT_EQ(c.l->valid_lft_, hint);
+
+ // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors
+ int32_t cltt = static_cast<int32_t>(c.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));
+
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(c.addr);
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(lease));
+}
+
+// This test verifies that renewal returns the min valid lifetime
+// when the client adds a too small hint in the DISCOVER.
+TEST_F(Dhcpv4SrvTest, RenewMinLifetime) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ struct ctx c = {
+ this, // test
+ srv.get(), // srv
+ IOAddress("192.0.2.106"), // addr
+ 100, // temp_valid
+ time(NULL) - 10, // temp_timestamp
+ // Generate client-id also sets client_id_ member
+ generateClientId(), // clientid
+ HWAddrPtr(), // hwaddr
+ Lease4Ptr(), // used
+ Lease4Ptr(), // l
+ OptionPtr(), // opt
+ Pkt4Ptr(), // req
+ Pkt4Ptr() // acka
+ };
+
+ // Add a dhcp-lease-time with too small valid lifetime hint.
+ // The min valid lifetime will be returned in the OFFER.
+ c.opt.reset(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME, 1000));
+
+ prepare(c);
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ // Note that T2 should be false for a reason which does not matter...
+ checkAddressParams(c.ack, subnet_, true, false, subnet_->getValid().getMin());
+
+ // Check that valid and cltt were really updated
+ EXPECT_EQ(c.l->valid_lft_, subnet_->getValid().getMin());
+
+ // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors
+ int32_t cltt = static_cast<int32_t>(c.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));
+
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(c.addr);
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(lease));
+}
+
+// This test verifies that renewal returns the max valid lifetime
+// when the client adds a too large hint in the DISCOVER.
+TEST_F(Dhcpv4SrvTest, RenewMaxLifetime) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ struct ctx c = {
+ this, // test
+ srv.get(), // srv
+ IOAddress("192.0.2.106"), // addr
+ 100, // temp_valid
+ time(NULL) - 10, // temp_timestamp
+ // Generate client-id also sets client_id_ member
+ generateClientId(), // clientid
+ HWAddrPtr(), // hwaddr
+ Lease4Ptr(), // used
+ Lease4Ptr(), // l
+ OptionPtr(), // opt
+ Pkt4Ptr(), // req
+ Pkt4Ptr() // acka
+ };
+
+ // Add a dhcp-lease-time with too large valid lifetime hint.
+ // The max valid lifetime will be returned in the OFFER.
+ c.opt.reset(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME, 5000));
+
+ prepare(c);
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(c.ack, subnet_, true, true, subnet_->getValid().getMax());
+
+ // Check that valid and cltt were really updated
+ EXPECT_EQ(c.l->valid_lft_, subnet_->getValid().getMax());
+
+ // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors
+ int32_t cltt = static_cast<int32_t>(c.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));
+
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(c.addr);
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(lease));
+}
+
+} // end of Renew*Lifetime
+
+// This test verifies that incoming RENEW can reuse an existing lease.
+TEST_F(Dhcpv4SrvTest, RenewCache) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ // Enable lease reuse.
+ subnet_->setCacheThreshold(.1);
+
+ const IOAddress addr("192.0.2.106");
+ const uint32_t temp_valid = subnet_->getValid();
+ const int delta = 100;
+ const time_t temp_timestamp = time(NULL) - delta;
+
+ // Generate client-id also sets client_id_ member
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_V4, addr));
+
+ // let's create a lease and put it in the LeaseMgr
+ uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER));
+ Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2,
+ &client_id_->getDuid()[0], client_id_->getDuid().size(),
+ temp_valid, temp_timestamp, subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database
+ Lease4Ptr l = LeaseMgrFactory::instance().getLease4(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->valid_lft_, temp_valid);
+ EXPECT_EQ(l->cltt_, temp_timestamp);
+
+ // Let's create a RENEW
+ Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+ req->setRemoteAddr(IOAddress(addr));
+ req->setYiaddr(addr);
+ req->setCiaddr(addr); // client's address
+ req->setIface("eth0");
+ req->setIndex(ETH0_INDEX);
+
+ req->addOption(clientid);
+ req->setHWAddr(hwaddr2);
+
+ // Pass it to the server and hope for a REPLY
+ Pkt4Ptr ack = srv->processRequest(req);
+
+ // Check if we get response at all
+ checkResponse(ack, DHCPACK, 1234);
+
+ // Check valid lifetime (temp_valid - age)
+ OptionUint32Ptr opt = boost::dynamic_pointer_cast<
+ OptionUint32>(ack->getOption(DHO_DHCP_LEASE_TIME));
+ ASSERT_TRUE(opt);
+ EXPECT_GE(subnet_->getValid() - delta, opt->getValue());
+ EXPECT_LE(subnet_->getValid() - delta - 10, opt->getValue());
+
+ // Check address
+ EXPECT_EQ(addr, ack->getYiaddr());
+
+ // Check T1
+ opt = boost::dynamic_pointer_cast<
+ OptionUint32>(ack->getOption(DHO_DHCP_RENEWAL_TIME));
+ ASSERT_TRUE(opt);
+ EXPECT_EQ(opt->getValue(), subnet_->getT1());
+
+ // Check T2
+ opt = boost::dynamic_pointer_cast<
+ OptionUint32>(ack->getOption(DHO_DHCP_REBINDING_TIME));
+ ASSERT_TRUE(opt);
+ EXPECT_EQ(opt->getValue(), subnet_->getT2());
+
+ // Check identifiers
+ checkServerId(ack, srv->getServerID());
+ checkClientId(ack, clientid);
+
+ // Check that the lease is really in the database
+ Lease4Ptr lease = checkLease(ack, clientid, req->getHWAddr(), addr);
+ ASSERT_TRUE(lease);
+
+ // Check that the lease was not updated
+ EXPECT_EQ(temp_timestamp, lease->cltt_);
+}
+
+// Exercises Dhcpv4Srv::buildCfgOptionList().
+TEST_F(Dhcpv4SrvTest, buildCfgOptionsList) {
+ configureServerIdentifier();
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ Pkt4Ptr query(new Pkt4(DHCPREQUEST, 1234));
+ query->addOption(generateClientId());
+ query->setHWAddr(generateHWAddr(6));
+ query->setIface("eth0");
+ query->setIndex(ETH0_INDEX);
+
+ {
+ SCOPED_TRACE("Pool value");
+
+ // Server id should come from subnet2's first pool.
+ buildCfgOptionTest(IOAddress("192.0.2.254"), query, IOAddress("192.0.2.101"), IOAddress("192.0.2.254"));
+ }
+
+ {
+ SCOPED_TRACE("Subnet value");
+
+ // Server id should come from subnet3.
+ buildCfgOptionTest(IOAddress("192.0.3.254"), query, IOAddress("192.0.3.101"), IOAddress("192.0.3.254"));
+ }
+
+ {
+ SCOPED_TRACE("Shared-network value");
+
+ // Server id should come from subnet4's shared-network.
+ buildCfgOptionTest(IOAddress("192.0.4.254"), query, IOAddress("192.0.4.101"), IOAddress("192.0.4.254"));
+ }
+
+ {
+ SCOPED_TRACE("Client-class value");
+
+ Pkt4Ptr query_with_classes(new Pkt4(DHCPREQUEST, 1234));
+ query_with_classes->addOption(generateClientId());
+ query_with_classes->setHWAddr(generateHWAddr(6));
+ query_with_classes->setIface("eth0");
+ query_with_classes->setIndex(ETH0_INDEX);
+ query_with_classes->addClass("foo");
+
+ // Server id should come from subnet5's client-class value.
+ buildCfgOptionTest(IOAddress("192.0.5.254"), query_with_classes, IOAddress("192.0.5.101"), IOAddress("192.0.5.254"));
+ }
+
+ {
+ SCOPED_TRACE("Global value if client class does not define it");
+
+ Pkt4Ptr query_with_classes(new Pkt4(DHCPREQUEST, 1234));
+ query_with_classes->addOption(generateClientId());
+ query_with_classes->setHWAddr(generateHWAddr(6));
+ query_with_classes->setIface("eth0");
+ query_with_classes->setIndex(ETH0_INDEX);
+ query_with_classes->addClass("bar");
+
+ // Server id should be global value as subnet6's client-class does not define it.
+ buildCfgOptionTest(IOAddress("10.0.0.254"), query_with_classes, IOAddress("192.0.6.101"), IOAddress("192.0.6.100"));
+ }
+
+ {
+ SCOPED_TRACE("Global value if client class does not define any option");
+
+ Pkt4Ptr query_with_classes(new Pkt4(DHCPREQUEST, 1234));
+ query_with_classes->addOption(generateClientId());
+ query_with_classes->setHWAddr(generateHWAddr(6));
+ query_with_classes->setIface("eth0");
+ query_with_classes->setIndex(ETH0_INDEX);
+ query_with_classes->addClass("xyz");
+
+ // Server id should be global value as subnet7's client-class does not define any option.
+ buildCfgOptionTest(IOAddress("10.0.0.254"), query_with_classes, IOAddress("192.0.7.101"), IOAddress("192.0.7.100"));
+ }
+
+ {
+ SCOPED_TRACE("Global value");
+
+ // Server id should be global value as lease is from subnet2's second pool.
+ buildCfgOptionTest(IOAddress("10.0.0.254"), query, IOAddress("192.0.2.201"), IOAddress("10.0.0.254"));
+ }
+}
+
+// This test verifies that the logic which matches server identifier in the
+// received message with server identifiers used by a server works correctly:
+// - a message with no server identifier is accepted,
+// - a message with a server identifier which matches one of the server
+// identifiers used by a server is accepted,
+// - a message with a server identifier which doesn't match any server
+// identifier used by a server, is not accepted.
+TEST_F(Dhcpv4SrvTest, acceptServerId) {
+ configureServerIdentifier();
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ NakedDhcpv4Srv srv(0);
+
+ Pkt4Ptr pkt(new Pkt4(DHCPREQUEST, 1234));
+ // If no server identifier option is present, the message is always
+ // accepted.
+ EXPECT_TRUE(srv.acceptServerId(pkt));
+
+ // Create definition of the server identifier option.
+ OptionDefinition def("server-identifier", DHO_DHCP_SERVER_IDENTIFIER,
+ DHCP4_OPTION_SPACE, "ipv4-address", false);
+
+ // Add a server identifier option which doesn't match server ids being
+ // used by the server. The accepted server ids are the IPv4 addresses
+ // configured on the interfaces. The 10.1.2.3 is not configured on
+ // any interfaces.
+ OptionCustomPtr other_serverid(new OptionCustom(def, Option::V4));
+ other_serverid->writeAddress(IOAddress("10.1.2.3"));
+ pkt->addOption(other_serverid);
+ EXPECT_FALSE(srv.acceptServerId(pkt));
+
+ // Remove the server identifier.
+ ASSERT_NO_THROW(pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER));
+
+ // Add a server id being an IPv4 address configured on eth1 interface.
+ // A DHCPv4 message holding this server identifier should be accepted.
+ OptionCustomPtr eth1_serverid(new OptionCustom(def, Option::V4));
+ eth1_serverid->writeAddress(IOAddress("192.0.2.3"));
+ ASSERT_NO_THROW(pkt->addOption(eth1_serverid));
+ EXPECT_TRUE(srv.acceptServerId(pkt));
+
+ // Remove the server identifier.
+ ASSERT_NO_THROW(pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER));
+
+ // Add a server id being an IPv4 address configured on eth0 interface.
+ // A DHCPv4 message holding this server identifier should be accepted.
+ OptionCustomPtr eth0_serverid(new OptionCustom(def, Option::V4));
+ eth0_serverid->writeAddress(IOAddress("10.0.0.1"));
+ ASSERT_NO_THROW(pkt->addOption(eth0_serverid));
+ EXPECT_TRUE(srv.acceptServerId(pkt));
+
+ // Remove the server identifier.
+ ASSERT_NO_THROW(pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER));
+
+ // Add a server id being an IPv4 address configured on subnet3.
+ // A DHCPv4 message holding this server identifier should be accepted.
+ OptionCustomPtr subnet_serverid(new OptionCustom(def, Option::V4));
+ subnet_serverid->writeAddress(IOAddress("192.0.3.254"));
+ ASSERT_NO_THROW(pkt->addOption(subnet_serverid));
+ EXPECT_TRUE(srv.acceptServerId(pkt));
+
+ // Remove the server identifier.
+ ASSERT_NO_THROW(pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER));
+
+ // Add a server id being an IPv4 address configured on shared network1.
+ // A DHCPv4 message holding this server identifier should be accepted.
+ OptionCustomPtr network_serverid(new OptionCustom(def, Option::V4));
+ network_serverid->writeAddress(IOAddress("192.0.4.254"));
+ ASSERT_NO_THROW(pkt->addOption(network_serverid));
+ EXPECT_TRUE(srv.acceptServerId(pkt));
+
+ // Remove the server identifier.
+ ASSERT_NO_THROW(pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER));
+
+ // Add a server id being an IPv4 address configured on client class.
+ // A DHCPv4 message holding this server identifier should be accepted.
+ Pkt4Ptr pkt_with_classes(new Pkt4(DHCPREQUEST, 1234));
+ OptionCustomPtr class_serverid(new OptionCustom(def, Option::V4));
+ class_serverid->writeAddress(IOAddress("192.0.5.254"));
+ ASSERT_NO_THROW(pkt_with_classes->addOption(class_serverid));
+ pkt_with_classes->addClass("foo");
+ EXPECT_TRUE(srv.acceptServerId(pkt_with_classes));
+
+ // Remove the server identifier.
+ ASSERT_NO_THROW(pkt_with_classes->delOption(DHO_DHCP_SERVER_IDENTIFIER));
+
+ // Add a server id being an IPv4 address configured on global level.
+ // The configured class does not define the server id option.
+ // A DHCPv4 message holding this server identifier should be accepted.
+ Pkt4Ptr pkt_with_classes_option_not_defined(new Pkt4(DHCPREQUEST, 1234));
+ OptionCustomPtr global_serverid(new OptionCustom(def, Option::V4));
+ global_serverid->writeAddress(IOAddress("10.0.0.254"));
+ ASSERT_NO_THROW(pkt_with_classes_option_not_defined->addOption(global_serverid));
+ pkt_with_classes_option_not_defined->addClass("bar");
+ EXPECT_TRUE(srv.acceptServerId(pkt_with_classes_option_not_defined));
+
+ // Remove the server identifier.
+ ASSERT_NO_THROW(pkt_with_classes_option_not_defined->delOption(DHO_DHCP_SERVER_IDENTIFIER));
+
+ // Add a server id being an IPv4 address configured on global level.
+ // The configured class does not define any option.
+ // A DHCPv4 message holding this server identifier should be accepted.
+ Pkt4Ptr pkt_with_classes_no_options(new Pkt4(DHCPREQUEST, 1234));
+ ASSERT_NO_THROW(pkt_with_classes_no_options->addOption(global_serverid));
+ pkt_with_classes_no_options->addClass("xyz");
+ EXPECT_TRUE(srv.acceptServerId(pkt_with_classes_no_options));
+
+ // Remove the server identifier.
+ ASSERT_NO_THROW(pkt_with_classes_no_options->delOption(DHO_DHCP_SERVER_IDENTIFIER));
+
+ // Add a server id being an IPv4 address configured on global level.
+ // A DHCPv4 message holding this server identifier should be accepted.
+ ASSERT_NO_THROW(pkt->addOption(global_serverid));
+ EXPECT_TRUE(srv.acceptServerId(pkt));
+
+ // Remove the server identifier.
+ ASSERT_NO_THROW(pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER));
+
+ OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_AGENT_OPTIONS);
+
+ OptionBuffer override_server_id_buf(IOAddress("10.0.0.128").toBytes());
+
+ // Create RAI option.
+ OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4));
+ OptionPtr rai_override_server_id(new Option(Option::V4,
+ RAI_OPTION_SERVER_ID_OVERRIDE,
+ override_server_id_buf));
+ rai->addOption(rai_override_server_id);
+
+ // Add a server id being an IPv4 address matching RAI sub-option 11
+ // (RAI_OPTION_SERVER_ID_OVERRIDE).
+ // A DHCPv4 message holding this server identifier should be accepted.
+ Pkt4Ptr pkt_with_override_server_id(new Pkt4(DHCPREQUEST, 1234));
+ OptionCustomPtr override_serverid(new OptionCustom(def, Option::V4));
+ override_serverid->writeAddress(IOAddress("10.0.0.128"));
+
+ ASSERT_NO_THROW(pkt_with_override_server_id->addOption(override_serverid));
+ ASSERT_NO_THROW(pkt_with_override_server_id->addOption(rai));
+ EXPECT_TRUE(srv.acceptServerId(pkt_with_override_server_id));
+
+ // Remove the server identifier.
+ ASSERT_NO_THROW(pkt_with_override_server_id->delOption(DHO_DHCP_SERVER_IDENTIFIER));
+}
+
+// @todo: Implement tests for rejecting renewals
+
+// This test verifies if the sanityCheck() really checks options presence.
+TEST_F(Dhcpv4SrvTest, sanityCheck) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ pkt->setHWAddr(generateHWAddr(6));
+
+ // Server-id is optional for information-request, so
+ EXPECT_NO_THROW(NakedDhcpv4Srv::sanityCheck(pkt, Dhcpv4Srv::OPTIONAL));
+
+ // Empty packet, no server-id
+ EXPECT_THROW(NakedDhcpv4Srv::sanityCheck(pkt, Dhcpv4Srv::MANDATORY),
+ RFCViolation);
+
+ pkt->addOption(srv->getServerID());
+
+ // Server-id is mandatory and present = no exception
+ EXPECT_NO_THROW(NakedDhcpv4Srv::sanityCheck(pkt, Dhcpv4Srv::MANDATORY));
+
+ // Server-id is forbidden, but present => exception
+ EXPECT_THROW(NakedDhcpv4Srv::sanityCheck(pkt, Dhcpv4Srv::FORBIDDEN),
+ RFCViolation);
+
+ // There's no client-id and no HWADDR. Server needs something to
+ // identify the client
+ pkt->setHWAddr(generateHWAddr(0));
+ EXPECT_THROW(NakedDhcpv4Srv::sanityCheck(pkt, Dhcpv4Srv::MANDATORY),
+ RFCViolation);
+}
+
+} // end of anonymous namespace
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+void
+Dhcpv4SrvTest::relayAgentInfoEcho() {
+ IfaceMgrTestConfig test_config(true);
+ NakedDhcpv4Srv srv(0);
+
+ // Use of the captured DHCPDISCOVER packet requires that
+ // subnet 10.254.226.0/24 is in use, because this packet
+ // contains the giaddr which belongs to this subnet and
+ // this giaddr is used to select the subnet
+ configure(CONFIGS[0]);
+
+ // Let's create a relayed DISCOVER. This particular relayed DISCOVER has
+ // added option 82 (relay agent info) with 3 suboptions. The server
+ // is supposed to echo it back in its response.
+ Pkt4Ptr dis;
+ ASSERT_NO_THROW(dis = PktCaptures::captureRelayedDiscover());
+
+ // Simulate that we have received that traffic
+ srv.fakeReceive(dis);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered buffer4_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
+ Pkt4Ptr offer = srv.fake_sent_.front();
+ ASSERT_TRUE(offer);
+
+ // Get Relay Agent Info from query...
+ OptionPtr rai_query = dis->getOption(DHO_DHCP_AGENT_OPTIONS);
+ ASSERT_TRUE(rai_query);
+
+ // Get Relay Agent Info from response...
+ OptionPtr rai_response = offer->getOption(DHO_DHCP_AGENT_OPTIONS);
+ ASSERT_TRUE(rai_response);
+
+ EXPECT_TRUE(rai_response->equals(rai_query));
+}
+
+void
+Dhcpv4SrvTest::badRelayAgentInfoEcho() {
+ IfaceMgrTestConfig test_config(true);
+ NakedDhcpv4Srv srv(0);
+
+ // Use of the captured DHCPDISCOVER packet requires that
+ // subnet 10.254.226.0/24 is in use, because this packet
+ // contains the giaddr which belongs to this subnet and
+ // this giaddr is used to select the subnet
+ configure(CONFIGS[0]);
+
+ // Let's create a relayed DISCOVER. This particular relayed DISCOVER has
+ // added option 82 (relay agent info) with a sub-option which does not
+ // fit in the option. Unpacking it gave an empty option which is
+ // supposed to not be echoed back in its response.
+ Pkt4Ptr dis;
+ ASSERT_NO_THROW(dis = PktCaptures::captureBadRelayedDiscover());
+
+ // Simulate that we have received that traffic
+ srv.fakeReceive(dis);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered buffer4_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
+ Pkt4Ptr offer = srv.fake_sent_.front();
+ ASSERT_TRUE(offer);
+
+ // Get Relay Agent Info from query...
+ OptionPtr rai_query = dis->getOption(DHO_DHCP_AGENT_OPTIONS);
+ ASSERT_TRUE(rai_query);
+ ASSERT_EQ(2, rai_query->len());
+
+ // Get Relay Agent Info from response...
+ OptionPtr rai_response = offer->getOption(DHO_DHCP_AGENT_OPTIONS);
+ ASSERT_FALSE(rai_response);
+}
+
+void
+Dhcpv4SrvTest::portsClientPort() {
+ IfaceMgrTestConfig test_config(true);
+ NakedDhcpv4Srv srv(0);
+
+ // By default te client port is supposed to be zero.
+ EXPECT_EQ(0, srv.client_port_);
+
+ // Use of the captured DHCPDISCOVER packet requires that
+ // subnet 10.254.226.0/24 is in use, because this packet
+ // contains the giaddr which belongs to this subnet and
+ // this giaddr is used to select the subnet
+ configure(CONFIGS[0]);
+ srv.client_port_ = 1234;
+
+ // Let's create a relayed DISCOVER. This particular relayed DISCOVER has
+ // added option 82 (relay agent info) with 3 suboptions. The server
+ // is supposed to echo it back in its response.
+ Pkt4Ptr dis;
+ ASSERT_NO_THROW(dis = PktCaptures::captureRelayedDiscover());
+
+ // Simulate that we have received that traffic
+ srv.fakeReceive(dis);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered buffer4_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
+ Pkt4Ptr offer = srv.fake_sent_.front();
+ ASSERT_TRUE(offer);
+
+ // Get Relay Agent Info from query...
+ EXPECT_EQ(srv.client_port_, offer->getRemotePort());
+}
+
+void
+Dhcpv4SrvTest::portsServerPort() {
+ IfaceMgrTestConfig test_config(true);
+
+ // Do not use DHCP4_SERVER_PORT here as 0 means don't open sockets.
+ NakedDhcpv4Srv srv(0);
+ EXPECT_EQ(0, srv.server_port_);
+
+ // Use of the captured DHCPDISCOVER packet requires that
+ // subnet 10.254.226.0/24 is in use, because this packet
+ // contains the giaddr which belongs to this subnet and
+ // this giaddr is used to select the subnet
+ configure(CONFIGS[0]);
+ srv.server_port_ = 1234;
+
+ // Let's create a relayed DISCOVER. This particular relayed DISCOVER has
+ // added option 82 (relay agent info) with 3 suboptions. The server
+ // is supposed to echo it back in its response.
+ Pkt4Ptr dis;
+ ASSERT_NO_THROW(dis = PktCaptures::captureRelayedDiscover());
+
+ // Simulate that we have received that traffic
+ srv.fakeReceive(dis);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered buffer4_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
+ Pkt4Ptr offer = srv.fake_sent_.front();
+ ASSERT_TRUE(offer);
+
+ // Get Relay Agent Info from query...
+ EXPECT_EQ(srv.server_port_, offer->getLocalPort());
+}
+
+void
+Dhcpv4SrvTest::loadConfigFile(const string& path) {
+ CfgMgr::instance().clear();
+
+ LibDHCP::clearRuntimeOptionDefs();
+
+ IfaceMgrTestConfig test_config(true);
+
+ // Do not use DHCP4_SERVER_PORT here as 0 means don't open sockets.
+ NakedDhcpv4Srv srv(0);
+ EXPECT_EQ(0, srv.server_port_);
+
+ ConfigBackendDHCPv4Mgr::instance().registerBackendFactory("mysql",
+ [](const db::DatabaseConnection::ParameterMap&) -> ConfigBackendDHCPv4Ptr {
+ return (ConfigBackendDHCPv4Ptr());
+ });
+
+ ConfigBackendDHCPv4Mgr::instance().registerBackendFactory("postgresql",
+ [](const db::DatabaseConnection::ParameterMap&) -> ConfigBackendDHCPv4Ptr {
+ return (ConfigBackendDHCPv4Ptr());
+ });
+
+ // 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());
+
+ Parser4Context parser;
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parser.parseFile(path, Parser4Context::PARSER_DHCP4));
+ ASSERT_TRUE(json);
+
+ // Check the logic next.
+ ConstElementPtr dhcp4 = json->get("Dhcp4");
+ ASSERT_TRUE(dhcp4);
+ ElementPtr mutable_config = boost::const_pointer_cast<Element>(dhcp4);
+ mutable_config->set(string("hooks-libraries"), Element::createList());
+ ASSERT_NO_THROW(Dhcpv4SrvTest::configure(dhcp4->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());
+}
+
+void
+Dhcpv4SrvTest::checkConfigFiles() {
+ IfaceMgrTestConfig test_config(true);
+ string path = CFG_EXAMPLES;
+ vector<string> examples = {
+ "advanced.json",
+#if defined (HAVE_MYSQL) && defined (HAVE_PGSQL)
+ "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",
+ "global-reservations.json",
+ "ha-load-balancing-primary.json",
+ "hooks.json",
+ "hooks-radius.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",
+ "single-subnet.json",
+ "vendor-specific.json",
+ "vivso.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 {
+
+TEST_F(Dhcpv4SrvTest, relayAgentInfoEcho) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ relayAgentInfoEcho();
+}
+
+TEST_F(Dhcpv4SrvTest, relayAgentInfoEchoMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ relayAgentInfoEcho();
+}
+
+TEST_F(Dhcpv4SrvTest, badRelayAgentInfoEcho) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ badRelayAgentInfoEcho();
+}
+
+TEST_F(Dhcpv4SrvTest, badRelayAgentInfoEchoMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ badRelayAgentInfoEcho();
+}
+
+TEST_F(Dhcpv4SrvTest, portsClientPort) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ portsClientPort();
+}
+
+TEST_F(Dhcpv4SrvTest, portsClientPortMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ portsClientPort();
+}
+
+TEST_F(Dhcpv4SrvTest, portsServerPort) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ portsServerPort();
+}
+
+TEST_F(Dhcpv4SrvTest, portsServerPortMultiTHreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ portsServerPort();
+}
+
+/// @brief Check that example files from documentation are valid (can be parsed
+/// and loaded).
+TEST_F(Dhcpv4SrvTest, checkConfigFiles) {
+ checkConfigFiles();
+}
+
+/// @todo Implement tests for subnetSelect See tests in dhcp6_srv_unittest.cc:
+/// selectSubnetAddr, selectSubnetIface, selectSubnetRelayLinkaddr,
+/// selectSubnetRelayInterfaceId. Note that the concept of interface-id is not
+/// present in the DHCPv4, so not everything is applicable directly.
+/// See ticket #3057
+
+// Checks whether the server uses default (0.0.0.0) siaddr value, unless
+// explicitly specified
+TEST_F(Dhcpv4SrvTest, siaddrDefault) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+ IOAddress hint("192.0.2.107");
+
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+ dis->setYiaddr(hint);
+ dis->setIface("eth1");
+ dis->setIndex(ETH1_INDEX);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv->processDiscover(dis);
+ ASSERT_TRUE(offer);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Verify that it is 0.0.0.0
+ EXPECT_EQ("0.0.0.0", offer->getSiaddr().toText());
+}
+
+// Checks whether the server uses specified siaddr value
+TEST_F(Dhcpv4SrvTest, siaddr) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+ subnet_->setSiaddr(IOAddress("192.0.2.123"));
+
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ dis->setIface("eth1");
+ dis->setIndex(ETH1_INDEX);
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv->processDiscover(dis);
+ ASSERT_TRUE(offer);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Verify that its value is proper
+ EXPECT_EQ("192.0.2.123", offer->getSiaddr().toText());
+}
+
+// Checks if the next-server defined as global value is overridden by subnet
+// specific value and returned in server messages. There's also similar test for
+// checking parser only configuration, see Dhcp4ParserTest.nextServerOverride in
+// config_parser_unittest.cc. This test was extended to other BOOTP fixed fields.
+TEST_F(Dhcpv4SrvTest, nextServerOverride) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ NakedDhcpv4Srv srv(0);
+
+ ConstElementPtr status;
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"next-server\": \"192.0.0.1\", "
+ "\"server-hostname\": \"nohost\", "
+ "\"boot-file-name\": \"nofile\", "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"next-server\": \"1.2.3.4\", "
+ " \"server-hostname\": \"some-name.example.org\", "
+ " \"boot-file-name\": \"bootfile.efi\", "
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config, true));
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+
+ CfgMgr::instance().commit();
+
+ // check if returned status is OK
+ ASSERT_TRUE(status);
+ comment_ = config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ dis->setIface("eth1");
+ dis->setIndex(ETH1_INDEX);
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv.processDiscover(dis);
+ ASSERT_TRUE(offer);
+ EXPECT_EQ(DHCPOFFER, offer->getType());
+
+ EXPECT_EQ("1.2.3.4", offer->getSiaddr().toText());
+ std::string sname("some-name.example.org");
+ uint8_t sname_buf[Pkt4::MAX_SNAME_LEN];
+ std::memset(sname_buf, 0, Pkt4::MAX_SNAME_LEN);
+ std::memcpy(sname_buf, sname.c_str(), sname.size());
+ EXPECT_EQ(0, std::memcmp(sname_buf, &offer->getSname()[0], Pkt4::MAX_SNAME_LEN));
+ std::string filename("bootfile.efi");
+ uint8_t filename_buf[Pkt4::MAX_FILE_LEN];
+ std::memset(filename_buf, 0, Pkt4::MAX_FILE_LEN);
+ std::memcpy(filename_buf, filename.c_str(), filename.size());
+ EXPECT_EQ(0, std::memcmp(filename_buf, &offer->getFile()[0], Pkt4::MAX_FILE_LEN));
+}
+
+// Checks if the next-server defined as global value is used in responses
+// when there is no specific value defined in subnet and returned to the client
+// properly. There's also similar test for checking parser only configuration,
+// see Dhcp4ParserTest.nextServerGlobal in config_parser_unittest.cc.
+TEST_F(Dhcpv4SrvTest, nextServerGlobal) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ NakedDhcpv4Srv srv(0);
+
+ ConstElementPtr status;
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"next-server\": \"192.0.0.1\", "
+ "\"server-hostname\": \"some-name.example.org\", "
+ "\"boot-file-name\": \"bootfile.efi\", "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config, true));
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+
+ CfgMgr::instance().commit();
+
+ // check if returned status is OK
+ ASSERT_TRUE(status);
+ comment_ = config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ dis->setIface("eth1");
+ dis->setIndex(ETH1_INDEX);
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv.processDiscover(dis);
+ ASSERT_TRUE(offer);
+ EXPECT_EQ(DHCPOFFER, offer->getType());
+
+ EXPECT_EQ("192.0.0.1", offer->getSiaddr().toText());
+ std::string sname("some-name.example.org");
+ uint8_t sname_buf[Pkt4::MAX_SNAME_LEN];
+ std::memset(sname_buf, 0, Pkt4::MAX_SNAME_LEN);
+ std::memcpy(sname_buf, sname.c_str(), sname.size());
+ EXPECT_EQ(0, std::memcmp(sname_buf, &offer->getSname()[0], Pkt4::MAX_SNAME_LEN));
+ std::string filename("bootfile.efi");
+ uint8_t filename_buf[Pkt4::MAX_FILE_LEN];
+ std::memset(filename_buf, 0, Pkt4::MAX_FILE_LEN);
+ std::memcpy(filename_buf, filename.c_str(), filename.size());
+ EXPECT_EQ(0, std::memcmp(filename_buf, &offer->getFile()[0], Pkt4::MAX_FILE_LEN));
+}
+
+// Checks if client packets are classified properly using match expressions.
+TEST_F(Dhcpv4SrvTest, matchClassification) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ NakedDhcpv4Srv srv(0);
+
+ // The router class matches incoming packets with foo in a host-name
+ // option (code 12) and sets an ip-forwarding option in the response.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
+ " \"subnet\": \"192.0.2.0/24\" } ], "
+ "\"client-classes\": [ "
+ "{ \"name\": \"router\", "
+ " \"option-data\": ["
+ " { \"name\": \"ip-forwarding\", "
+ " \"data\": \"true\" } ], "
+ " \"test\": \"option[12].text == 'foo'\" } ] }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Create packets with enough to select the subnet
+ OptionPtr clientid = generateClientId();
+ Pkt4Ptr query1(new Pkt4(DHCPDISCOVER, 1234));
+ query1->setRemoteAddr(IOAddress("192.0.2.1"));
+ query1->addOption(clientid);
+ query1->setIface("eth1");
+ query1->setIndex(ETH1_INDEX);
+ Pkt4Ptr query2(new Pkt4(DHCPDISCOVER, 1234));
+ query2->setRemoteAddr(IOAddress("192.0.2.1"));
+ query2->addOption(clientid);
+ query2->setIface("eth1");
+ query2->setIndex(ETH1_INDEX);
+ Pkt4Ptr query3(new Pkt4(DHCPDISCOVER, 1234));
+ query3->setRemoteAddr(IOAddress("192.0.2.1"));
+ query3->addOption(clientid);
+ query3->setIface("eth1");
+ query3->setIndex(ETH1_INDEX);
+
+ // Create and add a PRL option to the first 2 queries
+ OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+ DHO_DHCP_PARAMETER_REQUEST_LIST));
+ ASSERT_TRUE(prl);
+ prl->addValue(DHO_IP_FORWARDING);
+ query1->addOption(prl);
+ query2->addOption(prl);
+
+ // Create and add a host-name option to the first and last queries
+ OptionStringPtr hostname(new OptionString(Option::V4, 12, "foo"));
+ ASSERT_TRUE(hostname);
+ query1->addOption(hostname);
+ query3->addOption(hostname);
+
+ // Classify packets
+ srv.classifyPacket(query1);
+ srv.classifyPacket(query2);
+ srv.classifyPacket(query3);
+
+ // 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"));
+
+ // Process queries
+ Pkt4Ptr response1 = srv.processDiscover(query1);
+ Pkt4Ptr response2 = srv.processDiscover(query2);
+ Pkt4Ptr response3 = srv.processDiscover(query3);
+
+ // Classification processing should add an ip-forwarding option
+ OptionPtr opt1 = response1->getOption(DHO_IP_FORWARDING);
+ EXPECT_TRUE(opt1);
+
+ // But only for the first query: second was not classified
+ OptionPtr opt2 = response2->getOption(DHO_IP_FORWARDING);
+ EXPECT_FALSE(opt2);
+
+ // But only for the first query: third has no PRL
+ OptionPtr opt3 = response3->getOption(DHO_IP_FORWARDING);
+ EXPECT_FALSE(opt3);
+}
+
+// Checks if client packets are classified properly using match expressions
+// using option names
+TEST_F(Dhcpv4SrvTest, matchClassificationOptionName) {
+ NakedDhcpv4Srv srv(0);
+
+ // The router class matches incoming packets with foo in a host-name
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
+ " \"subnet\": \"192.0.2.0/24\" } ], "
+ "\"client-classes\": [ "
+ "{ \"name\": \"router\", "
+ " \"test\": \"option[host-name].text == 'foo'\" } ] }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Create a packet with enough to select the subnet
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ query->setRemoteAddr(IOAddress("192.0.2.1"));
+
+ // Create and add a host-name option to the query
+ OptionStringPtr hostname(new OptionString(Option::V4, 12, "foo"));
+ ASSERT_TRUE(hostname);
+ query->addOption(hostname);
+
+ // Classify packets
+ srv.classifyPacket(query);
+
+ // The query should be in the router class
+ EXPECT_TRUE(query->inClass("router"));
+}
+
+// Checks if client packets are classified properly using match expressions
+// using option names and definitions
+TEST_F(Dhcpv4SrvTest, matchClassificationOptionDef) {
+ NakedDhcpv4Srv srv(0);
+
+ // The router class matches incoming packets with foo in a defined
+ // option
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
+ " \"subnet\": \"192.0.2.0/24\" } ], "
+ "\"client-classes\": [ "
+ "{ \"name\": \"router\", "
+ " \"test\": \"option[my-host-name].text == 'foo'\" } ], "
+ "\"option-def\": [ {"
+ " \"name\": \"my-host-name\", "
+ " \"code\": 250, "
+ " \"type\": \"string\" } ] }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Create a packet with enough to select the subnet
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ query->setRemoteAddr(IOAddress("192.0.2.1"));
+
+ // Create and add a my-host-name option to the query
+ OptionStringPtr hostname(new OptionString(Option::V4, 250, "foo"));
+ ASSERT_TRUE(hostname);
+ query->addOption(hostname);
+
+ // Classify packets
+ srv.classifyPacket(query);
+
+ // The query should be in the router class
+ EXPECT_TRUE(query->inClass("router"));
+}
+
+// Checks subnet options have the priority over class options
+TEST_F(Dhcpv4SrvTest, subnetClassPriority) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ NakedDhcpv4Srv srv(0);
+
+ // Subnet sets an ip-forwarding option in the response.
+ // The router class matches incoming packets with foo in a host-name
+ // option (code 12) and sets an ip-forwarding option in the response.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"option-data\": ["
+ " { \"name\": \"ip-forwarding\", "
+ " \"data\": \"false\" } ] } ], "
+ "\"client-classes\": [ "
+ "{ \"name\": \"router\","
+ " \"option-data\": ["
+ " { \"name\": \"ip-forwarding\", "
+ " \"data\": \"true\" } ], "
+ " \"test\": \"option[12].text == 'foo'\" } ] }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Create a packet with enough to select the subnet and go through
+ // the DISCOVER processing
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ query->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ query->addOption(clientid);
+ query->setIface("eth1");
+ query->setIndex(ETH1_INDEX);
+
+ // Create and add a PRL option to the query
+ OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+ DHO_DHCP_PARAMETER_REQUEST_LIST));
+ ASSERT_TRUE(prl);
+ prl->addValue(DHO_IP_FORWARDING);
+ query->addOption(prl);
+
+ // Create and add a host-name option to the query
+ OptionStringPtr hostname(new OptionString(Option::V4, 12, "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
+ Pkt4Ptr response = srv.processDiscover(query);
+
+ // Processing should add an ip-forwarding option
+ OptionPtr opt = response->getOption(DHO_IP_FORWARDING);
+ 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(Dhcpv4SrvTest, subnetGlobalPriority) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ NakedDhcpv4Srv srv(0);
+
+ // Subnet and global set an ip-forwarding option in the response.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"option-data\": ["
+ " { \"name\": \"ip-forwarding\", "
+ " \"data\": \"false\" } ] } ], "
+ "\"option-data\": ["
+ " { \"name\": \"ip-forwarding\", "
+ " \"data\": \"true\" } ] }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Create a packet with enough to select the subnet and go through
+ // the DISCOVER processing
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ query->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ query->addOption(clientid);
+ query->setIface("eth1");
+ query->setIndex(ETH1_INDEX);
+
+ // Create and add a PRL option to the query
+ OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+ DHO_DHCP_PARAMETER_REQUEST_LIST));
+ ASSERT_TRUE(prl);
+ prl->addValue(DHO_IP_FORWARDING);
+ query->addOption(prl);
+
+ // Create and add a host-name option to the query
+ OptionStringPtr hostname(new OptionString(Option::V4, 12, "foo"));
+ ASSERT_TRUE(hostname);
+ query->addOption(hostname);
+
+ // Process the query
+ Pkt4Ptr response = srv.processDiscover(query);
+
+ // Processing should add an ip-forwarding option
+ OptionPtr opt = response->getOption(DHO_IP_FORWARDING);
+ 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(Dhcpv4SrvTest, classGlobalPriority) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ NakedDhcpv4Srv srv(0);
+
+ // A global ip-forwarding option is set in the response.
+ // The router class matches incoming packets with foo in a host-name
+ // option (code 12) and sets an ip-forwarding option in the response.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
+ " \"subnet\": \"192.0.2.0/24\" } ], "
+ "\"option-data\": ["
+ " { \"name\": \"ip-forwarding\", "
+ " \"data\": \"false\" } ], "
+ "\"client-classes\": [ "
+ "{ \"name\": \"router\","
+ " \"option-data\": ["
+ " { \"name\": \"ip-forwarding\", "
+ " \"data\": \"true\" } ], "
+ " \"test\": \"option[12].text == 'foo'\" } ] }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Create a packet with enough to select the subnet and go through
+ // the DISCOVER processing
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ query->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ query->addOption(clientid);
+ query->setIface("eth1");
+ query->setIndex(ETH1_INDEX);
+
+ // Create and add a PRL option to the query
+ OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+ DHO_DHCP_PARAMETER_REQUEST_LIST));
+ ASSERT_TRUE(prl);
+ prl->addValue(DHO_IP_FORWARDING);
+ query->addOption(prl);
+
+ // Create and add a host-name option to the query
+ OptionStringPtr hostname(new OptionString(Option::V4, 12, "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
+ Pkt4Ptr response = srv.processDiscover(query);
+
+ // Processing should add an ip-forwarding option
+ OptionPtr opt = response->getOption(DHO_IP_FORWARDING);
+ 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(Dhcpv4SrvTest, classGlobalPersistency) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ NakedDhcpv4Srv srv(0);
+
+ // A global ip-forwarding option is set in the response.
+ // The router class matches incoming packets with foo in a host-name
+ // option (code 12) and sets an ip-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.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
+ " \"subnet\": \"192.0.2.0/24\" } ], "
+ "\"option-data\": ["
+ " { \"name\": \"ip-forwarding\", "
+ " \"data\": \"false\", "
+ " \"always-send\": true } ], "
+ "\"client-classes\": [ "
+ "{ \"name\": \"router\","
+ " \"option-data\": ["
+ " { \"name\": \"ip-forwarding\", "
+ " \"data\": \"true\", "
+ " \"always-send\": false } ], "
+ " \"test\": \"option[12].text == 'foo'\" } ] }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Create a packet with enough to select the subnet and go through
+ // the DISCOVER processing
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ query->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ query->addOption(clientid);
+ query->setIface("eth1");
+ query->setIndex(ETH1_INDEX);
+
+ // Do not add a PRL
+ OptionPtr prl = query->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST);
+ EXPECT_FALSE(prl);
+
+ // Create and add a host-name option to the query
+ OptionStringPtr hostname(new OptionString(Option::V4, 12, "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
+ Pkt4Ptr response = srv.processDiscover(query);
+
+ // Processing should add an ip-forwarding option
+ OptionPtr opt = response->getOption(DHO_IP_FORWARDING);
+ 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 if the client-class field is indeed used for subnet selection.
+// Note that packet classification is already checked in Dhcpv4SrvTest
+// .*Classification above.
+TEST_F(Dhcpv4SrvTest, clientClassify) {
+
+ // 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.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"client-class\": \"foo\", "
+ " \"subnet\": \"192.0.2.0/24\" }, "
+ "{ \"pools\": [ { \"pool\": \"192.0.3.1 - 192.0.3.100\" } ],"
+ " \"client-class\": \"xyzzy\", "
+ " \"subnet\": \"192.0.3.0/24\" } "
+ "],"
+ "\"valid-lifetime\": 4000 }";
+
+ ASSERT_NO_THROW(configure(config, true, false));
+
+ // Create a simple packet that we'll use for classification
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ dis->setCiaddr(IOAddress("192.0.2.1"));
+ dis->setIface("eth0");
+ dis->setIndex(ETH0_INDEX);
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+
+ // This discover does not belong to foo class, so it will not
+ // be serviced
+ bool drop = false;
+ EXPECT_FALSE(srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+
+ // Let's add the packet to bar class and try again.
+ dis->addClass("bar");
+
+ // Still not supported, because it belongs to wrong class.
+ EXPECT_FALSE(srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+
+ // Let's add it to matching class.
+ dis->addClass("foo");
+
+ // This time it should work
+ EXPECT_TRUE(srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+}
+
+// Checks if the client-class field is indeed used for pool selection.
+TEST_F(Dhcpv4SrvTest, clientPoolClassify) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ NakedDhcpv4Srv 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.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { "
+ " \"pool\": \"192.0.2.1 - 192.0.2.100\", "
+ " \"client-class\": \"foo\" }, "
+ " { \"pool\": \"192.0.3.1 - 192.0.3.100\", "
+ " \"client-class\": \"xyzzy\" } ], "
+ " \"subnet\": \"192.0.0.0/16\" } "
+ "],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config, true));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+
+ CfgMgr::instance().commit();
+
+ // check if returned status is OK
+ ASSERT_TRUE(status);
+ comment_ = config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ // Create a simple packet that we'll use for classification
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ dis->setCiaddr(IOAddress("192.0.2.1"));
+ dis->setIface("eth0");
+ dis->setIndex(ETH0_INDEX);
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+
+ // This discover does not belong to foo class, so it will not
+ // be serviced
+ Pkt4Ptr offer = srv.processDiscover(dis);
+ EXPECT_FALSE(offer);
+
+ // Let's add the packet to bar class and try again.
+ dis->addClass("bar");
+
+ // Still not supported, because it belongs to wrong class.
+ offer = srv.processDiscover(dis);
+ EXPECT_FALSE(offer);
+
+ // Let's add it to matching class.
+ dis->addClass("foo");
+
+ // This time it should work
+ offer = srv.processDiscover(dis);
+ ASSERT_TRUE(offer);
+ EXPECT_EQ(DHCPOFFER, offer->getType());
+ EXPECT_FALSE(offer->getYiaddr().isV4Zero());
+}
+
+// Checks if the KNOWN built-in classes is indeed used for pool selection.
+TEST_F(Dhcpv4SrvTest, clientPoolClassifyKnown) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ NakedDhcpv4Srv srv(0);
+
+ // This test configures 2 pools.
+ // The first one requires reservation, the second does the opposite.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { "
+ " \"pool\": \"192.0.2.1 - 192.0.2.100\", "
+ " \"client-class\": \"KNOWN\" }, "
+ " { \"pool\": \"192.0.3.1 - 192.0.3.100\", "
+ " \"client-class\": \"UNKNOWN\" } ], "
+ " \"subnet\": \"192.0.0.0/16\" } "
+ "],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config, true));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+
+ CfgMgr::instance().commit();
+
+ // check if returned status is OK
+ ASSERT_TRUE(status);
+ comment_ = config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ // Create a simple packet that we'll use for classification
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ dis->setCiaddr(IOAddress("192.0.2.1"));
+ dis->setIface("eth0");
+ dis->setIndex(ETH0_INDEX);
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+
+ // First pool requires reservation so the second will be used
+ Pkt4Ptr offer = srv.processDiscover(dis);
+ ASSERT_TRUE(offer);
+ EXPECT_EQ(DHCPOFFER, offer->getType());
+ EXPECT_EQ("192.0.3.1", offer->getYiaddr().toText());
+}
+
+// Checks if the UNKNOWN built-in classes is indeed used for pool selection.
+TEST_F(Dhcpv4SrvTest, clientPoolClassifyUnknown) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ NakedDhcpv4Srv srv(0);
+
+ // This test configures 2 pools.
+ // The first one requires no reservation, the second does the opposite.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { "
+ " \"pool\": \"192.0.2.1 - 192.0.2.100\", "
+ " \"client-class\": \"UNKNOWN\" }, "
+ " { \"pool\": \"192.0.3.1 - 192.0.3.100\", "
+ " \"client-class\": \"KNOWN\" } ], "
+ " \"subnet\": \"192.0.0.0/16\", "
+ " \"reservations\": [ { "
+ " \"hw-address\": \"00:00:00:11:22:33\", "
+ " \"hostname\": \"foo.bar\" } ] } "
+ "],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config, true));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+
+ CfgMgr::instance().commit();
+
+ // check if returned status is OK
+ ASSERT_TRUE(status);
+ comment_ = config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ // Create a simple packet that we'll use for classification
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ dis->setCiaddr(IOAddress("192.0.2.1"));
+ dis->setIface("eth0");
+ dis->setIndex(ETH0_INDEX);
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+
+ // Set hardware address / identifier
+ const HWAddr& hw = HWAddr::fromText("00:00:00:11:22:33");
+ HWAddrPtr hw_addr(new HWAddr(hw));
+ dis->setHWAddr(hw_addr);
+
+ // First pool requires no reservation so the second will be used
+ Pkt4Ptr offer = srv.processDiscover(dis);
+ ASSERT_TRUE(offer);
+ EXPECT_EQ(DHCPOFFER, offer->getType());
+ EXPECT_EQ("192.0.3.1", offer->getYiaddr().toText());
+}
+
+// Verifies private option deferred processing
+TEST_F(Dhcpv4SrvTest, privateOption) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ NakedDhcpv4Srv srv(0);
+
+ // Same than option43Class but with private options
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"client-classes\": [ "
+ "{ \"name\": \"private\", "
+ " \"test\": \"option[234].exists\", "
+ " \"option-def\": [ "
+ " { \"code\": 245, "
+ " \"name\": \"privint\", "
+ " \"type\": \"uint32\" } ],"
+ " \"option-data\": [ "
+ " { \"code\": 234, "
+ " \"data\": \"01\" }, "
+ " { \"name\": \"privint\", "
+ " \"data\": \"12345678\" } ] } ] }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Create a packet with enough to select the subnet and go through
+ // the DISCOVER processing
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ query->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ query->addOption(clientid);
+ query->setIface("eth1");
+ query->setIndex(ETH1_INDEX);
+
+ // Create and add a private option with code 234
+ OptionBuffer buf;
+ buf.push_back(0x01);
+ OptionPtr opt1(new Option(Option::V4, 234, buf));
+ query->addOption(opt1);
+ query->getDeferredOptions().push_back(234);
+
+ // Create and add a private option with code 245
+ buf.clear();
+ buf.push_back(0x87);
+ buf.push_back(0x65);
+ buf.push_back(0x43);
+ buf.push_back(0x21);
+ OptionPtr opt2(new Option(Option::V4, 245, buf));
+ query->addOption(opt2);
+ query->getDeferredOptions().push_back(245);
+
+ // Create and add a PRL option to the query
+ OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+ DHO_DHCP_PARAMETER_REQUEST_LIST));
+ ASSERT_TRUE(prl);
+ prl->addValue(234);
+ prl->addValue(245);
+ query->addOption(prl);
+
+ srv.classifyPacket(query);
+ ASSERT_NO_THROW(srv.deferredUnpack(query));
+
+ // Check if the option 245 was re-unpacked
+ opt2 = query->getOption(245);
+ OptionUint32Ptr opt32 = boost::dynamic_pointer_cast<OptionUint32>(opt2);
+ EXPECT_TRUE(opt32);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv.processDiscover(query);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Processing should add an option with code 234
+ OptionPtr opt = offer->getOption(234);
+ EXPECT_TRUE(opt);
+
+ // And an option with code 245
+ opt = offer->getOption(245);
+ ASSERT_TRUE(opt);
+ // Verifies the content
+ opt32 = boost::dynamic_pointer_cast<OptionUint32>(opt);
+ ASSERT_TRUE(opt32);
+ EXPECT_EQ(12345678, opt32->getValue());
+}
+
+// Checks effect of persistency (aka always-true) flag on the PRL
+TEST_F(Dhcpv4SrvTest, prlPersistency) {
+ IfaceMgrTestConfig test_config(true);
+
+ ASSERT_NO_THROW(configure(CONFIGS[2]));
+
+ // Create a packet with enough to select the subnet and go through
+ // the DISCOVER processing
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ query->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ query->addOption(clientid);
+ query->setIface("eth1");
+ query->setIndex(ETH1_INDEX);
+
+ // Create and add a PRL option for another option
+ OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+ DHO_DHCP_PARAMETER_REQUEST_LIST));
+ ASSERT_TRUE(prl);
+ prl->addValue(DHO_ARP_CACHE_TIMEOUT);
+ query->addOption(prl);
+
+ // Create and add a host-name option to the query
+ OptionStringPtr hostname(new OptionString(Option::V4, 12, "foo"));
+ ASSERT_TRUE(hostname);
+ query->addOption(hostname);
+
+ // Let the server process it.
+ Pkt4Ptr response = srv_.processDiscover(query);
+
+ // Processing should add an ip-forwarding option
+ ASSERT_TRUE(response->getOption(DHO_IP_FORWARDING));
+ // But no default-ip-ttl
+ ASSERT_FALSE(response->getOption(DHO_DEFAULT_IP_TTL));
+ // Nor an arp-cache-timeout
+ ASSERT_FALSE(response->getOption(DHO_ARP_CACHE_TIMEOUT));
+
+ // Reset PRL adding default-ip-ttl
+ query->delOption(DHO_DHCP_PARAMETER_REQUEST_LIST);
+ prl->addValue(DHO_DEFAULT_IP_TTL);
+ query->addOption(prl);
+
+ // Let the server process it again.
+ response = srv_.processDiscover(query);
+
+ // Processing should add an ip-forwarding option
+ ASSERT_TRUE(response->getOption(DHO_IP_FORWARDING));
+ // and now a default-ip-ttl
+ ASSERT_TRUE(response->getOption(DHO_DEFAULT_IP_TTL));
+ // and still no arp-cache-timeout
+ ASSERT_FALSE(response->getOption(DHO_ARP_CACHE_TIMEOUT));
+}
+
+// Checks if relay IP address specified in the relay-info structure in
+// subnet4 is being used properly.
+TEST_F(Dhcpv4SrvTest, 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\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"192.0.2.2 - 192.0.2.100\" } ],"
+ " \"relay\": { "
+ " \"ip-address\": \"192.0.5.1\""
+ " },"
+ " \"subnet\": \"192.0.2.0/24\" }, "
+ "{ \"pools\": [ { \"pool\": \"192.0.3.1 - 192.0.3.100\" } ],"
+ " \"relay\": { "
+ " \"ip-address\": \"192.0.5.2\""
+ " },"
+ " \"subnet\": \"192.0.3.0/24\" } "
+ "],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Use this config to set up the server
+ ASSERT_NO_THROW(configure(config, true, false));
+
+ // Let's get the subnet configuration objects
+ const Subnet4Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+ ASSERT_EQ(2, subnets->size());
+
+ // Let's get them for easy reference
+ Subnet4Ptr subnet1 = *subnets->begin();
+ Subnet4Ptr subnet2 = *std::next(subnets->begin());
+ ASSERT_TRUE(subnet1);
+ ASSERT_TRUE(subnet2);
+
+ // Let's create a packet.
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ dis->setIface("eth0");
+ dis->setIndex(ETH0_INDEX);
+ dis->setHops(1);
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+
+ // This is just a sanity check, we're using regular method: ciaddr 192.0.2.1
+ // belongs to the first subnet, so it is selected
+ dis->setGiaddr(IOAddress("192.0.2.1"));
+ bool drop = false;
+ EXPECT_TRUE(subnet1 == srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+
+ // Relay belongs to the second subnet, so it should be selected.
+ dis->setGiaddr(IOAddress("192.0.3.1"));
+ EXPECT_TRUE(subnet2 == srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+
+ // Now let's check if the relay override for the first subnets works
+ dis->setGiaddr(IOAddress("192.0.5.1"));
+ EXPECT_TRUE(subnet1 == srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+
+ // The same check for the second subnet...
+ dis->setGiaddr(IOAddress("192.0.5.2"));
+ EXPECT_TRUE(subnet2 == srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+
+ // And finally, let's check if mis-matched relay address will end up
+ // in not selecting a subnet at all
+ dis->setGiaddr(IOAddress("192.0.5.3"));
+ EXPECT_FALSE(srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+
+ // Finally, check that the relay override works only with relay address
+ // (GIADDR) and does not affect client address (CIADDR)
+ dis->setGiaddr(IOAddress("0.0.0.0"));
+ dis->setHops(0);
+ dis->setCiaddr(IOAddress("192.0.5.1"));
+ EXPECT_FALSE(srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+}
+
+// Checks if relay IP address specified in the relay-info structure can be
+// used together with client-classification.
+TEST_F(Dhcpv4SrvTest, 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".
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"192.0.2.2 - 192.0.2.100\" } ],"
+ " \"client-class\": \"foo\", "
+ " \"relay\": { "
+ " \"ip-address\": \"192.0.5.1\""
+ " },"
+ " \"subnet\": \"192.0.2.0/24\" }, "
+ "{ \"pools\": [ { \"pool\": \"192.0.3.1 - 192.0.3.100\" } ],"
+ " \"relay\": { "
+ " \"ip-address\": \"192.0.5.1\""
+ " },"
+ " \"subnet\": \"192.0.3.0/24\" } "
+ "],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Use this config to set up the server
+ ASSERT_NO_THROW(configure(config, true, false));
+
+ const Subnet4Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+ ASSERT_EQ(2, subnets->size());
+
+ // Let's get them for easy reference
+ Subnet4Ptr subnet1 = *subnets->begin();
+ Subnet4Ptr subnet2 = *std::next(subnets->begin());
+ ASSERT_TRUE(subnet1);
+ ASSERT_TRUE(subnet2);
+
+ // Let's create a packet.
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ dis->setIface("eth0");
+ dis->setIndex(ETH0_INDEX);
+ dis->setHops(1);
+ dis->setGiaddr(IOAddress("192.0.5.1"));
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+
+ // 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(dis, 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.
+ dis->addClass("foo");
+ EXPECT_TRUE(subnet1 == srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+}
+
+// Checks if a RAI link selection sub-option works as expected
+TEST_F(Dhcpv4SrvTest, relayLinkSelect) {
+
+ // We have 3 subnets defined.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"192.0.2.2 - 192.0.2.100\" } ],"
+ " \"relay\": { "
+ " \"ip-address\": \"192.0.5.1\""
+ " },"
+ " \"subnet\": \"192.0.2.0/24\" }, "
+ "{ \"pools\": [ { \"pool\": \"192.0.3.1 - 192.0.3.100\" } ],"
+ " \"subnet\": \"192.0.3.0/24\" }, "
+ "{ \"pools\": [ { \"pool\": \"192.0.4.1 - 192.0.4.100\" } ],"
+ " \"client-class\": \"foo\", "
+ " \"subnet\": \"192.0.4.0/24\" } "
+ "],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Use this config to set up the server
+ ASSERT_NO_THROW(configure(config, true, false));
+
+ // Let's get the subnet configuration objects
+ const Subnet4Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+ ASSERT_EQ(3, subnets->size());
+
+ // Let's get them for easy reference
+ auto subnet_it = subnets->begin();
+ Subnet4Ptr subnet1 = *subnet_it;
+ ++subnet_it;
+ Subnet4Ptr subnet2 = *subnet_it;
+ ++subnet_it;
+ Subnet4Ptr subnet3 = *subnet_it;
+ ASSERT_TRUE(subnet1);
+ ASSERT_TRUE(subnet2);
+ ASSERT_TRUE(subnet3);
+
+ // Let's create a packet.
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ dis->setIface("eth0");
+ dis->setIndex(ETH0_INDEX);
+ dis->setHops(1);
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+
+ // Let's create a Relay Agent Information option
+ OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_AGENT_OPTIONS);
+ ASSERT_TRUE(rai_def);
+ OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4));
+ ASSERT_TRUE(rai);
+ IOAddress addr("192.0.3.2");
+ OptionPtr ols(new Option(Option::V4,
+ RAI_OPTION_LINK_SELECTION,
+ addr.toBytes()));
+ ASSERT_TRUE(ols);
+ rai->addOption(ols);
+
+ // This is just a sanity check, we're using regular method: ciaddr 192.0.3.1
+ // belongs to the second subnet, so it is selected
+ dis->setGiaddr(IOAddress("192.0.3.1"));
+ bool drop = false;
+ EXPECT_TRUE(subnet2 == srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+
+ // Setup a relay override for the first subnet as it has a high precedence
+ dis->setGiaddr(IOAddress("192.0.5.1"));
+ EXPECT_TRUE(subnet1 == srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+
+ // Put a RAI to select back the second subnet as it has
+ // the highest precedence
+ dis->addOption(rai);
+ EXPECT_TRUE(subnet2 == srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+
+ // Subnet select option has a lower precedence
+ OptionDefinitionPtr sbnsel_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_SUBNET_SELECTION);
+ ASSERT_TRUE(sbnsel_def);
+ OptionCustomPtr sbnsel(new OptionCustom(*sbnsel_def, Option::V4));
+ ASSERT_TRUE(sbnsel);
+ sbnsel->writeAddress(IOAddress("192.0.2.3"));
+ dis->addOption(sbnsel);
+ EXPECT_TRUE(subnet2 == srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+
+ // But, when RAI exists without the link selection option, we should
+ // fall back to the subnet selection option.
+ rai->delOption(RAI_OPTION_LINK_SELECTION);
+ dis->delOption(DHO_DHCP_AGENT_OPTIONS);
+ dis->addOption(rai);
+ dis->setGiaddr(IOAddress("192.0.4.1"));
+ EXPECT_TRUE(subnet1 == srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+
+ // Check client-classification still applies
+ IOAddress addr_foo("192.0.4.2");
+ ols.reset(new Option(Option::V4, RAI_OPTION_LINK_SELECTION,
+ addr_foo.toBytes()));
+ dis->delOption(DHO_SUBNET_SELECTION);
+ dis->delOption(DHO_DHCP_AGENT_OPTIONS);
+ rai->addOption(ols);
+ dis->addOption(rai);
+
+ // Note it shall fail (vs. try the next criterion).
+ EXPECT_FALSE(srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+ // Add the packet to the class and check again: now it shall succeed
+ dis->addClass("foo");
+ EXPECT_TRUE(subnet3 == srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+
+ // Check it fails with a bad address in the sub-option
+ IOAddress addr_bad("10.0.0.1");
+ ols.reset(new Option(Option::V4, RAI_OPTION_LINK_SELECTION,
+ addr_bad.toBytes()));
+ rai->delOption(RAI_OPTION_LINK_SELECTION);
+ dis->delOption(DHO_DHCP_AGENT_OPTIONS);
+ rai->addOption(ols);
+ dis->addOption(rai);
+ EXPECT_FALSE(srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+}
+
+// Checks if a subnet selection option works as expected
+TEST_F(Dhcpv4SrvTest, subnetSelect) {
+
+ // We have 3 subnets defined.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"192.0.2.2 - 192.0.2.100\" } ],"
+ " \"relay\": { "
+ " \"ip-address\": \"192.0.5.1\""
+ " },"
+ " \"subnet\": \"192.0.2.0/24\" }, "
+ "{ \"pools\": [ { \"pool\": \"192.0.3.1 - 192.0.3.100\" } ],"
+ " \"subnet\": \"192.0.3.0/24\" }, "
+ "{ \"pools\": [ { \"pool\": \"192.0.4.1 - 192.0.4.100\" } ],"
+ " \"client-class\": \"foo\", "
+ " \"subnet\": \"192.0.4.0/24\" } "
+ "],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Use this config to set up the server
+ ASSERT_NO_THROW(configure(config, true, false));
+
+ // Let's get the subnet configuration objects
+ const Subnet4Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+ ASSERT_EQ(3, subnets->size());
+
+ // Let's get them for easy reference
+ auto subnet_it = subnets->begin();
+ Subnet4Ptr subnet1 = *subnet_it;
+ ++subnet_it;
+ Subnet4Ptr subnet2 = *subnet_it;
+ ++subnet_it;
+ Subnet4Ptr subnet3 = *subnet_it;
+ ASSERT_TRUE(subnet1);
+ ASSERT_TRUE(subnet2);
+ ASSERT_TRUE(subnet3);
+
+ // Let's create a packet.
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ dis->setIface("eth0");
+ dis->setIndex(ETH0_INDEX);
+ dis->setHops(1);
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+
+ // Let's create a Subnet Selection option
+ OptionDefinitionPtr sbnsel_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_SUBNET_SELECTION);
+ ASSERT_TRUE(sbnsel_def);
+ OptionCustomPtr sbnsel(new OptionCustom(*sbnsel_def, Option::V4));
+ ASSERT_TRUE(sbnsel);
+ sbnsel->writeAddress(IOAddress("192.0.3.2"));
+
+ // This is just a sanity check, we're using regular method: ciaddr 192.0.3.1
+ // belongs to the second subnet, so it is selected
+ dis->setGiaddr(IOAddress("192.0.3.1"));
+ bool drop = false;
+ EXPECT_TRUE(subnet2 == srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+
+ // Setup a relay override for the first subnet as it has a high precedence
+ dis->setGiaddr(IOAddress("192.0.5.1"));
+ EXPECT_TRUE(subnet1 == srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+
+ // Put a subnet select option to select back the second subnet as
+ // it has the second highest precedence
+ dis->addOption(sbnsel);
+ EXPECT_TRUE(subnet2 == srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+
+ // Check client-classification still applies
+ sbnsel->writeAddress(IOAddress("192.0.4.2"));
+ // Note it shall fail (vs. try the next criterion).
+ EXPECT_FALSE(srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+ // Add the packet to the class and check again: now it shall succeed
+ dis->addClass("foo");
+ EXPECT_TRUE(subnet3 == srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+
+ // Check it fails with a bad address in the sub-option
+ sbnsel->writeAddress(IOAddress("10.0.0.1"));
+ EXPECT_FALSE(srv_.selectSubnet(dis, drop));
+ EXPECT_FALSE(drop);
+}
+
+// This test verifies that the direct message is dropped when it has been
+// received by the server via an interface for which there is no subnet
+// configured. It also checks that the message is not dropped (is processed)
+// when it is relayed or unicast.
+TEST_F(Dhcpv4SrvTest, acceptDirectRequest) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ NakedDhcpv4Srv srv(0);
+
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1234));
+ // Set Giaddr and local server's unicast address, but don't set hops.
+ // Hops value should not matter. The server will treat the message
+ // with the hops value of 0 and non-zero giaddr as relayed.
+ pkt->setGiaddr(IOAddress("192.0.10.1"));
+ pkt->setRemoteAddr(IOAddress("0.0.0.0"));
+ pkt->setLocalAddr(IOAddress("192.0.2.3"));
+ pkt->setIface("eth1");
+ pkt->setIndex(ETH1_INDEX);
+ EXPECT_TRUE(srv.accept(pkt));
+
+ // Let's set hops and check that the message is still accepted as
+ // a relayed message.
+ pkt->setHops(1);
+ EXPECT_TRUE(srv.accept(pkt));
+
+ // Make it a direct message but keep unicast server's address. The
+ // messages sent to unicast address should be accepted as they are
+ // most likely to renew existing leases. The server should respond
+ // to renews so they have to be accepted and processed.
+ pkt->setHops(0);
+ pkt->setGiaddr(IOAddress("0.0.0.0"));
+ EXPECT_TRUE(srv.accept(pkt));
+
+ // Direct message is now sent to a broadcast address. The server
+ // should accept this message because it has been received via
+ // eth1 for which there is a subnet configured (see test fixture
+ // class constructor).
+ pkt->setLocalAddr(IOAddress("255.255.255.255"));
+ EXPECT_TRUE(srv.accept(pkt));
+
+ // For eth0, there is no subnet configured. Such message is expected
+ // to be silently dropped.
+ pkt->setIface("eth0");
+ pkt->setIndex(ETH0_INDEX);
+ EXPECT_FALSE(srv.accept(pkt));
+
+ // But, if the message is unicast it should be accepted, even though
+ // it has been received via eth0.
+ pkt->setLocalAddr(IOAddress("10.0.0.1"));
+ EXPECT_TRUE(srv.accept(pkt));
+
+ // For the DHCPINFORM the ciaddr should be set or at least the source
+ // address.
+ pkt->setType(DHCPINFORM);
+ pkt->setRemoteAddr(IOAddress("10.0.0.101"));
+ EXPECT_TRUE(srv.accept(pkt));
+
+ // When neither ciaddr nor source address is present, the packet should
+ // be dropped.
+ pkt->setRemoteAddr(IOAddress("0.0.0.0"));
+ EXPECT_FALSE(srv.accept(pkt));
+
+ // When ciaddr is set, the packet should be accepted.
+ pkt->setCiaddr(IOAddress("10.0.0.1"));
+ EXPECT_TRUE(srv.accept(pkt));
+}
+
+// This test checks that the server rejects a message with invalid type.
+TEST_F(Dhcpv4SrvTest, acceptMessageType) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ NakedDhcpv4Srv srv(0);
+
+ // Specify messages to be accepted by the server.
+ int allowed[] = {
+ DHCPDISCOVER,
+ DHCPREQUEST,
+ DHCPRELEASE,
+ DHCPDECLINE,
+ DHCPINFORM
+ };
+ size_t allowed_size = sizeof(allowed) / sizeof(allowed[0]);
+ // Check that the server actually accepts these message types.
+ for (size_t i = 0; i < allowed_size; ++i) {
+ EXPECT_TRUE(srv.acceptMessageType(Pkt4Ptr(new Pkt4(allowed[i], 1234))))
+ << "Test failed for message type " << i;
+ }
+ // Specify messages which server is supposed to drop.
+ int not_allowed[] = {
+ DHCPOFFER,
+ DHCPACK,
+ DHCPNAK,
+ DHCPLEASEQUERY,
+ DHCPLEASEUNASSIGNED,
+ DHCPLEASEUNKNOWN,
+ DHCPLEASEACTIVE,
+ DHCPBULKLEASEQUERY,
+ DHCPLEASEQUERYDONE,
+ };
+ size_t not_allowed_size = sizeof(not_allowed) / sizeof(not_allowed[0]);
+ // Actually check that the server will drop these messages.
+ for (size_t i = 0; i < not_allowed_size; ++i) {
+ EXPECT_FALSE(srv.acceptMessageType(Pkt4Ptr(new Pkt4(not_allowed[i],
+ 1234))))
+ << "Test failed for message type " << i;
+ }
+
+ // Verify that we drop packets with no option 53
+ // Make a BOOTP packet (i.e. no option 53)
+ std::vector<uint8_t> bin;
+ const char* bootp_txt =
+ "01010601002529b629b600000000000000000000000000000ace5001944452fe711700"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "000000000000000000000000000000000000000000000000000063825363521b010400"
+ "020418020600237453fc48090b0000118b06010401020300ff00000000000000000000"
+ "0000000000000000000000000000000000000000";
+
+ isc::util::encode::decodeHex(bootp_txt, bin);
+ Pkt4Ptr pkt(new Pkt4(&bin[0], bin.size()));
+ pkt->unpack();
+ ASSERT_EQ(DHCP_NOTYPE, pkt->getType());
+ EXPECT_FALSE(srv.acceptMessageType(Pkt4Ptr(new Pkt4(&bin[0], bin.size()))));
+
+ // Verify that we drop packets with types >= DHCP_TYPES_EOF
+ // Make Discover with type changed to 0xff
+ std::vector<uint8_t> bin2;
+ const char* invalid_msg_type =
+ "010106015d05478d000000000000000000000000000000000afee20120e52ab8151400"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000638253633501ff3707"
+ "0102030407067d3c0a646f63736973332e303a7d7f0000118b7a010102057501010102"
+ "010303010104010105010106010107010f0801100901030a01010b01180c01010d0200"
+ "400e0200100f010110040000000211010014010015013f160101170101180104190104"
+ "1a01041b01201c01021d01081e01201f01102001102101022201012301002401002501"
+ "01260200ff2701012b59020345434d030b45434d3a45524f55544552040d3242523232"
+ "39553430303434430504312e3034060856312e33332e30330707322e332e3052320806"
+ "30303039354209094347333030304443520a074e657467656172fe01083d0fff2ab815"
+ "140003000120e52ab81514390205dc5219010420000002020620e52ab8151409090000"
+ "118b0401020300ff";
+
+ bin.clear();
+ isc::util::encode::decodeHex(invalid_msg_type, bin);
+ pkt.reset(new Pkt4(&bin[0], bin.size()));
+ pkt->unpack();
+ ASSERT_EQ(0xff, pkt->getType());
+ EXPECT_FALSE(srv.acceptMessageType(pkt));
+}
+
+// Test checks whether statistic is bumped up appropriately when Decline
+// message is received.
+TEST_F(Dhcpv4SrvTest, statisticsDecline) {
+ NakedDhcpv4Srv srv(0);
+
+ pretendReceivingPkt(srv, CONFIGS[0], DHCPDECLINE, "pkt4-decline-received");
+}
+
+// Test checks whether statistic is bumped up appropriately when Offer
+// message is received (this should never happen in a sane network).
+TEST_F(Dhcpv4SrvTest, statisticsOfferRcvd) {
+ NakedDhcpv4Srv srv(0);
+
+ pretendReceivingPkt(srv, CONFIGS[0], DHCPOFFER, "pkt4-offer-received");
+}
+
+// Test checks whether statistic is bumped up appropriately when Ack
+// message is received (this should never happen in a sane network).
+TEST_F(Dhcpv4SrvTest, statisticsAckRcvd) {
+ NakedDhcpv4Srv srv(0);
+
+ pretendReceivingPkt(srv, CONFIGS[0], DHCPACK, "pkt4-ack-received");
+}
+
+// Test checks whether statistic is bumped up appropriately when Nak
+// message is received (this should never happen in a sane network).
+TEST_F(Dhcpv4SrvTest, statisticsNakRcvd) {
+ NakedDhcpv4Srv srv(0);
+
+ pretendReceivingPkt(srv, CONFIGS[0], DHCPNAK, "pkt4-nak-received");
+}
+
+// Test checks whether statistic is bumped up appropriately when Release
+// message is received.
+TEST_F(Dhcpv4SrvTest, statisticsReleaseRcvd) {
+ NakedDhcpv4Srv srv(0);
+
+ pretendReceivingPkt(srv, CONFIGS[0], DHCPRELEASE, "pkt4-release-received");
+}
+
+// Test checks whether statistic is bumped up appropriately when unknown
+// message is received.
+TEST_F(Dhcpv4SrvTest, statisticsUnknownRcvd) {
+ NakedDhcpv4Srv srv(0);
+
+ pretendReceivingPkt(srv, CONFIGS[0], 200, "pkt4-unknown-received");
+
+ // There should also be pkt4-receive-drop stat bumped up
+ using namespace isc::stats;
+ StatsMgr& mgr = StatsMgr::instance();
+ ObservationPtr drop_stat = mgr.getObservation("pkt4-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 verifies that the server is able to handle an empty client-id
+// in incoming client message.
+TEST_F(Dhcpv4SrvTest, emptyClientId) {
+ IfaceMgrTestConfig test_config(true);
+ Dhcp4Client client;
+
+ EXPECT_NO_THROW(configure(CONFIGS[0], *client.getServer()));
+
+ // Tell the client to not send client-id on its own.
+ client.includeClientId("");
+
+ // Instead, tell him to send this extra option, which happens to be
+ // an empty client-id.
+ OptionPtr empty_client_id(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER));
+ 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.doDORA());
+}
+
+// This test verifies that the server is able to handle too long client-id
+// in incoming client message.
+TEST_F(Dhcpv4SrvTest, tooLongClientId) {
+ IfaceMgrTestConfig test_config(true);
+ Dhcp4Client client;
+
+ EXPECT_NO_THROW(configure(CONFIGS[0], *client.getServer()));
+
+ // Tell the client to not send client-id on its own.
+ client.includeClientId("");
+
+ // Instead, tell him to send this extra option, which happens to be
+ // an empty client-id.
+ std::vector<uint8_t> data(250, 250);
+ OptionPtr long_client_id(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER,
+ data));
+ client.addExtraOption(long_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.doDORA());
+}
+
+// Checks if user-contexts are parsed properly.
+TEST_F(Dhcpv4SrvTest, userContext) {
+
+ IfaceMgrTestConfig test_config(true);
+
+ NakedDhcpv4Srv srv(0);
+
+ // This config has one subnet with user-context with one
+ // pool (also with context). Make sure the configuration could be accepted.
+ cout << CONFIGS[3] << endl;
+ EXPECT_NO_THROW(configure(CONFIGS[3]));
+
+ // Now make sure the data was not lost.
+ ConstSrvConfigPtr cfg = CfgMgr::instance().getCurrentCfg();
+ const Subnet4Collection* subnets = cfg->getCfgSubnets4()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(1, subnets->size());
+
+ // Let's get the subnet and check its context.
+ Subnet4Ptr subnet1 = *subnets->begin();
+ ASSERT_TRUE(subnet1);
+ ASSERT_TRUE(subnet1->getContext());
+ EXPECT_EQ("{ \"secure\": false }", subnet1->getContext()->str());
+
+ // Ok, not get the sole pool in it and check its context, too.
+ PoolCollection pools = subnet1->getPools(Lease::TYPE_V4);
+ ASSERT_EQ(1, pools.size());
+ ASSERT_TRUE(pools[0]);
+ ASSERT_TRUE(pools[0]->getContext());
+ EXPECT_EQ("{ \"value\": 42 }", pools[0]->getContext()->str());
+}
+
+// Verify that fixed fields are set from classes in the same order
+// as class options.
+TEST_F(Dhcpv4SrvTest, fixedFieldsInClassOrder) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ NakedDhcpv4Srv srv(0);
+
+ std::string config = R"(
+ {
+ "interfaces-config": { "interfaces": [ "*" ] },
+ "client-classes": [
+ {
+ "name":"one",
+ "server-hostname": "server_one",
+ "next-server": "192.0.2.111",
+ "boot-file-name":"one.boot",
+ "option-data": [
+ {
+ "name": "domain-name",
+ "data": "one.example.com"
+ }]
+ },
+ {
+ "name":"two",
+ "server-hostname": "server_two",
+ "next-server":"192.0.2.222",
+ "boot-file-name":"two.boot",
+ "option-data": [
+ {
+ "name": "domain-name",
+ "data": "two.example.com"
+ }]
+ },
+ {
+ "name":"next-server-only",
+ "next-server":"192.0.2.100"
+ },
+ {
+ "name":"server-hostname-only",
+ "server-hostname": "server_only"
+ },
+ {
+ "name":"bootfile-only",
+ "boot-file-name": "only.boot"
+ }],
+
+ "subnet4": [
+ {
+ "subnet": "192.0.2.0/24",
+ "pools": [ { "pool": "192.0.2.1 - 192.0.2.100" } ],
+ "reservations": [
+ {
+ "hw-address": "08:00:27:25:d3:01",
+ "client-classes": [ "one", "two" ]
+ },
+ {
+ "hw-address": "08:00:27:25:d3:02",
+ "client-classes": [ "two", "one" ]
+ },
+ {
+ "hw-address": "08:00:27:25:d3:03",
+ "client-classes": [ "server-hostname-only", "bootfile-only", "next-server-only" ]
+ }]
+ }]
+ }
+ )";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW_LOG(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ struct Scenario {
+ std::string hw_str_;
+ std::string exp_classes_;
+ std::string exp_server_hostname_;
+ std::string exp_next_server_;
+ std::string exp_bootfile_;
+ std::string exp_domain_name_;
+ };
+
+ const std::vector<Scenario> scenarios = {
+ {
+ "08:00:27:25:d3:01",
+ "ALL, one, two, KNOWN",
+ "server_one",
+ "192.0.2.111",
+ "one.boot",
+ "one.example.com"
+ },
+ {
+ "08:00:27:25:d3:02",
+ "ALL, two, one, KNOWN",
+ "server_two",
+ "192.0.2.222",
+ "two.boot",
+ "two.example.com"
+ },
+ {
+ "08:00:27:25:d3:03",
+ "ALL, server-hostname-only, bootfile-only, next-server-only, KNOWN",
+ "server_only",
+ "192.0.2.100",
+ "only.boot",
+ ""
+ }
+ };
+
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.hw_str_); {
+ // Build a DISCOVER
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ query->setRemoteAddr(IOAddress("192.0.2.1"));
+ query->setIface("eth1");
+
+ HWAddrPtr hw_addr(new HWAddr(HWAddr::fromText(scenario.hw_str_, 10)));
+ query->setHWAddr(hw_addr);
+
+ // Process it.
+ Pkt4Ptr response = srv.processDiscover(query);
+
+ // Make sure class list is as expected.
+ ASSERT_EQ(scenario.exp_classes_, query->getClasses().toText());
+
+ // Now check the fixed fields.
+ checkStringInBuffer(scenario.exp_server_hostname_, response->getSname());
+ EXPECT_EQ(scenario.exp_next_server_, response->getSiaddr().toText());
+ checkStringInBuffer(scenario.exp_bootfile_, response->getFile());
+
+ // Check domain name option.
+ OptionPtr opt = response->getOption(DHO_DOMAIN_NAME);
+ if (scenario.exp_domain_name_.empty()) {
+ ASSERT_FALSE(opt);
+ } else {
+ ASSERT_TRUE(opt);
+ OptionStringPtr opstr = boost::dynamic_pointer_cast<OptionString>(opt);
+ ASSERT_TRUE(opstr);
+ EXPECT_EQ(scenario.exp_domain_name_, opstr->getValue());
+ }
+ }
+ }
+}
+
+/// @todo: Implement proper tests for MySQL lease/host database,
+/// see ticket #4214.
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.cc b/src/bin/dhcp4/tests/dhcp4_test_utils.cc
new file mode 100644
index 0000000..6916da5
--- /dev/null
+++ b/src/bin/dhcp4/tests/dhcp4_test_utils.cc
@@ -0,0 +1,980 @@
+// Copyright (C) 2013-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 <asiolink/io_address.h>
+#include <cc/data.h>
+#include <cc/command_interpreter.h>
+#include <dhcp4/json_config_parser.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp/tests/pkt_captures.h>
+#include <dhcpsrv/cfg_db_access.h>
+#include <dhcpsrv/cfg_multi_threading.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <log/logger_support.h>
+#include <stats/stats_mgr.h>
+#include <sstream>
+
+using namespace std;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+BaseServerTest::BaseServerTest()
+ : original_datadir_(CfgMgr::instance().getDataDir()) {
+ CfgMgr::instance().setDataDir(TEST_DATA_BUILDDIR);
+}
+
+BaseServerTest::~BaseServerTest() {
+ // Remove default lease file.
+ std::ostringstream s2;
+ s2 << CfgMgr::instance().getDataDir() << "/kea-leases4.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 it.
+ isc::log::initLogger();
+}
+
+Dhcpv4SrvTest::Dhcpv4SrvTest()
+ : rcode_(-1), srv_(0), multi_threading_(false) {
+
+ // Wipe any existing statistics
+ isc::stats::StatsMgr::instance().removeAll();
+
+ subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1000,
+ 2000, 3000));
+ pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.110")));
+ subnet_->addPool(pool_);
+
+ // Add Router option.
+ Option4AddrLstPtr opt_routers(new Option4AddrLst(DHO_ROUTERS));
+ opt_routers->setAddress(IOAddress("192.0.2.2"));
+ subnet_->getCfgOption()->add(opt_routers, false, DHCP4_OPTION_SPACE);
+
+ CfgMgr::instance().clear();
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet_);
+ CfgMgr::instance().commit();
+
+ LibDHCP::clearRuntimeOptionDefs();
+
+ // Let's wipe all existing statistics.
+ isc::stats::StatsMgr::instance().removeAll();
+
+ // Reset the thread pool.
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+}
+
+Dhcpv4SrvTest::~Dhcpv4SrvTest() {
+ // Make sure that we revert to default value
+ CfgMgr::instance().clear();
+
+ LibDHCP::clearRuntimeOptionDefs();
+
+ // Let's wipe all existing statistics.
+ isc::stats::StatsMgr::instance().removeAll();
+
+ // Reset the thread pool.
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+}
+
+void Dhcpv4SrvTest::addPrlOption(Pkt4Ptr& pkt) {
+
+ OptionUint8ArrayPtr option_prl =
+ OptionUint8ArrayPtr(new OptionUint8Array(Option::V4,
+ DHO_DHCP_PARAMETER_REQUEST_LIST));
+
+ // Let's request options that have been configured for the subnet.
+ option_prl->addValue(DHO_DOMAIN_NAME_SERVERS);
+ option_prl->addValue(DHO_DOMAIN_NAME);
+ option_prl->addValue(DHO_LOG_SERVERS);
+ option_prl->addValue(DHO_COOKIE_SERVERS);
+ // Let's also request the option that hasn't been configured. In such
+ // case server should ignore request for this particular option.
+ option_prl->addValue(DHO_LPR_SERVERS);
+ // And add 'Parameter Request List' option into the DISCOVER packet.
+ pkt->addOption(option_prl);
+}
+
+void
+Dhcpv4SrvTest::configureRequestedOptions() {
+ // dns-servers
+ Option4AddrLstPtr
+ option_dns_servers(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS));
+ option_dns_servers->addAddress(IOAddress("192.0.2.1"));
+ option_dns_servers->addAddress(IOAddress("192.0.2.100"));
+ ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_dns_servers, false, DHCP4_OPTION_SPACE));
+
+ // domain-name
+ OptionDefinition def("domain-name", DHO_DOMAIN_NAME, DHCP4_OPTION_SPACE,
+ OPT_FQDN_TYPE);
+ OptionCustomPtr option_domain_name(new OptionCustom(def, Option::V4));
+ option_domain_name->writeFqdn("example.com");
+ subnet_->getCfgOption()->add(option_domain_name, false, DHCP4_OPTION_SPACE);
+
+ // log-servers
+ Option4AddrLstPtr option_log_servers(new Option4AddrLst(DHO_LOG_SERVERS));
+ option_log_servers->addAddress(IOAddress("192.0.2.2"));
+ option_log_servers->addAddress(IOAddress("192.0.2.10"));
+ ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_log_servers, false, DHCP4_OPTION_SPACE));
+
+ // cookie-servers
+ Option4AddrLstPtr option_cookie_servers(new Option4AddrLst(DHO_COOKIE_SERVERS));
+ option_cookie_servers->addAddress(IOAddress("192.0.2.1"));
+ ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_cookie_servers, false, DHCP4_OPTION_SPACE));
+}
+
+void
+Dhcpv4SrvTest::configureServerIdentifier() {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ CfgSubnets4Ptr subnets = cfg_mgr.getStagingCfg()->getCfgSubnets4();
+
+ // Build and add subnet2.
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 1200, 2400, 3600, 2));
+ Pool4Ptr pool(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.200")));
+ // Add server identifier to the pool.
+ OptionCustomPtr server_id = makeServerIdOption(IOAddress("192.0.2.254"));
+ CfgOptionPtr cfg_option = pool->getCfgOption();
+ cfg_option->add(server_id, false, DHCP4_OPTION_SPACE);
+ subnet2->addPool(pool);
+
+ // Add a second pool.
+ pool.reset(new Pool4(IOAddress("192.0.2.201"), IOAddress("192.0.2.220")));
+ subnet2->addPool(pool);
+
+ subnets->add(subnet2);
+
+ // Build and add subnet3.
+ Triplet<uint32_t> unspec;
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.3.0"), 24, unspec, unspec, 3600, 3));
+ pool.reset(new Pool4(IOAddress("192.0.3.100"), IOAddress("192.0.3.200")));
+ subnet3->addPool(pool);
+ subnet3->setT1Percent(0.5);
+ subnet3->setT2Percent(0.75);
+ subnet3->setCalculateTeeTimes(true);
+
+ // Add server identifier.
+ server_id = makeServerIdOption(IOAddress("192.0.3.254"));
+ cfg_option = subnet3->getCfgOption();
+ cfg_option->add(server_id, false, DHCP4_OPTION_SPACE);
+
+ subnets->add(subnet3);
+
+ // Build and add subnet4.
+ Subnet4Ptr subnet4(new Subnet4(IOAddress("192.0.4.0"), 24, unspec, unspec, 3600, 4));
+ pool.reset(new Pool4(IOAddress("192.0.4.100"), IOAddress("192.0.4.200")));
+ subnet4->addPool(pool);
+ subnet4->setCalculateTeeTimes(false);
+
+ subnets->add(subnet4);
+
+ // Build and add subnet5.
+ Subnet4Ptr subnet5(new Subnet4(IOAddress("192.0.5.0"), 24, unspec, unspec, 3600, 5));
+ pool.reset(new Pool4(IOAddress("192.0.5.100"), IOAddress("192.0.5.200")));
+ subnet5->addPool(pool);
+ subnet5->setCalculateTeeTimes(false);
+
+ subnets->add(subnet5);
+
+ CfgOptionPtr options(new CfgOption());
+ OptionDescriptor desc(false);
+ desc.option_ = makeServerIdOption(IOAddress("192.0.5.254"));
+ options->add(desc, DHCP4_OPTION_SPACE);
+ CfgMgr::instance().getStagingCfg()->getClientClassDictionary()->addClass("foo", ExpressionPtr(), "", true, false, options);
+ subnet5->requireClientClass("foo");
+
+ // Build and add subnet6.
+ Subnet4Ptr subnet6(new Subnet4(IOAddress("192.0.6.0"), 24, unspec, unspec, 3600, 6));
+ pool.reset(new Pool4(IOAddress("192.0.6.100"), IOAddress("192.0.6.200")));
+ subnet6->addPool(pool);
+ subnet6->setCalculateTeeTimes(false);
+
+ subnets->add(subnet6);
+
+ options.reset(new CfgOption());
+ OptionDescriptor desc_other(false);
+ desc_other.option_ = makeFqdnListOption();
+ options->add(desc_other, DHCP4_OPTION_SPACE);
+ CfgMgr::instance().getStagingCfg()->getClientClassDictionary()->addClass("bar", ExpressionPtr(), "", true, false, options);
+ subnet6->requireClientClass("bar");
+
+ // Build and add subnet7.
+ Subnet4Ptr subnet7(new Subnet4(IOAddress("192.0.7.0"), 24, unspec, unspec, 3600, 7));
+ pool.reset(new Pool4(IOAddress("192.0.7.100"), IOAddress("192.0.7.200")));
+ subnet7->addPool(pool);
+ subnet7->setCalculateTeeTimes(false);
+
+ subnets->add(subnet7);
+
+ options.reset();
+ CfgMgr::instance().getStagingCfg()->getClientClassDictionary()->addClass("xyz", ExpressionPtr(), "", true, false, options);
+ subnet7->requireClientClass("xyz");
+
+ // Build and add a shared-network.
+ CfgSharedNetworks4Ptr networks = cfg_mgr.getStagingCfg()->getCfgSharedNetworks4();
+ SharedNetwork4Ptr network1(new SharedNetwork4("one"));
+ network1->add(subnet4);
+
+ // Add server identifier.
+ server_id = makeServerIdOption(IOAddress("192.0.4.254"));
+ cfg_option = network1->getCfgOption();
+ cfg_option->add(server_id, false, DHCP4_OPTION_SPACE);
+
+ networks->add(network1);
+
+ // Add a global server identifier.
+ cfg_option = cfg_mgr.getStagingCfg()->getCfgOption();
+ server_id = makeServerIdOption(IOAddress("10.0.0.254"));
+ cfg_option->add(server_id, false, DHCP4_OPTION_SPACE);
+
+ // Commit the config.
+ cfg_mgr.commit();
+}
+
+void
+Dhcpv4SrvTest::messageCheck(const Pkt4Ptr& q, const Pkt4Ptr& a) {
+ ASSERT_TRUE(q);
+ ASSERT_TRUE(a);
+
+ EXPECT_EQ(q->getHops(), a->getHops());
+ EXPECT_EQ(q->getIface(), a->getIface());
+ EXPECT_EQ(q->getIndex(), a->getIndex());
+ EXPECT_EQ(q->getGiaddr(), a->getGiaddr());
+ // When processing an incoming packet the remote address
+ // is copied as a src address, and the source address is
+ // copied as a remote address to the response.
+ EXPECT_TRUE(q->getLocalHWAddr() == a->getLocalHWAddr());
+ EXPECT_TRUE(q->getRemoteHWAddr() == a->getRemoteHWAddr());
+
+ // Check that the server identifier is present in the response.
+ // Presence (or absence) of other options is checked elsewhere.
+ EXPECT_TRUE(a->getOption(DHO_DHCP_SERVER_IDENTIFIER));
+
+ // Check that something is offered
+ EXPECT_NE("0.0.0.0", a->getYiaddr().toText());
+}
+
+::testing::AssertionResult
+Dhcpv4SrvTest::basicOptionsPresent(const Pkt4Ptr& pkt) {
+ std::ostringstream errmsg;
+ errmsg << "option missing in the response";
+ if (!pkt->getOption(DHO_DOMAIN_NAME)) {
+ return (::testing::AssertionFailure(::testing::Message()
+ << "domain-name " << errmsg.str()));
+
+ } else if (!pkt->getOption(DHO_DOMAIN_NAME_SERVERS)) {
+ return (::testing::AssertionFailure(::testing::Message()
+ << "dns-servers " << errmsg.str()));
+
+ } else if (!pkt->getOption(DHO_SUBNET_MASK)) {
+ return (::testing::AssertionFailure(::testing::Message()
+ << "subnet-mask " << errmsg.str()));
+
+ } else if (!pkt->getOption(DHO_ROUTERS)) {
+ return (::testing::AssertionFailure(::testing::Message() << "routers "
+ << errmsg.str()));
+
+ } else if (!pkt->getOption(DHO_DHCP_LEASE_TIME)) {
+ return (::testing::AssertionFailure(::testing::Message() <<
+ "dhcp-lease-time " << errmsg.str()));
+
+ }
+
+ return (::testing::AssertionSuccess());
+}
+
+::testing::AssertionResult
+Dhcpv4SrvTest::noBasicOptions(const Pkt4Ptr& pkt) {
+ std::ostringstream errmsg;
+ errmsg << "option present in the response";
+ if (pkt->getOption(DHO_DOMAIN_NAME)) {
+ return (::testing::AssertionFailure(::testing::Message()
+ << "domain-name " << errmsg.str()));
+
+ } else if (pkt->getOption(DHO_DOMAIN_NAME_SERVERS)) {
+ return (::testing::AssertionFailure(::testing::Message()
+ << "dns-servers " << errmsg.str()));
+
+ } else if (pkt->getOption(DHO_SUBNET_MASK)) {
+ return (::testing::AssertionFailure(::testing::Message()
+ << "subnet-mask " << errmsg.str()));
+
+ } else if (pkt->getOption(DHO_ROUTERS)) {
+ return (::testing::AssertionFailure(::testing::Message() << "routers "
+ << errmsg.str()));
+
+ } else if (pkt->getOption(DHO_DHCP_LEASE_TIME)) {
+ return (::testing::AssertionFailure(::testing::Message()
+ << "dhcp-lease-time " << errmsg.str()));
+
+ }
+ return (::testing::AssertionSuccess());
+}
+
+::testing::AssertionResult
+Dhcpv4SrvTest::requestedOptionsPresent(const Pkt4Ptr& pkt) {
+ std::ostringstream errmsg;
+ errmsg << "option missing in the response";
+ if (!pkt->getOption(DHO_LOG_SERVERS)) {
+ return (::testing::AssertionFailure(::testing::Message()
+ << "log-servers " << errmsg.str()));
+
+ } else if (!pkt->getOption(DHO_COOKIE_SERVERS)) {
+ return (::testing::AssertionFailure(::testing::Message()
+ << "cookie-servers " << errmsg.str()));
+
+ }
+ return (::testing::AssertionSuccess());
+}
+
+::testing::AssertionResult
+Dhcpv4SrvTest::noRequestedOptions(const Pkt4Ptr& pkt) {
+ std::ostringstream errmsg;
+ errmsg << "option present in the response";
+ if (pkt->getOption(DHO_LOG_SERVERS)) {
+ return (::testing::AssertionFailure(::testing::Message()
+ << "log-servers " << errmsg.str()));
+
+ } else if (pkt->getOption(DHO_COOKIE_SERVERS)) {
+ return (::testing::AssertionFailure(::testing::Message()
+ << "cookie-servers " << errmsg.str()));
+
+ }
+ return (::testing::AssertionSuccess());
+}
+
+OptionPtr
+Dhcpv4SrvTest::generateClientId(size_t size /*= 4*/) {
+
+ OptionBuffer clnt_id(size);
+ for (size_t i = 0; i < size; i++) {
+ clnt_id[i] = 100 + i;
+ }
+
+ client_id_ = ClientIdPtr(new ClientId(clnt_id));
+
+ return (OptionPtr(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER,
+ clnt_id.begin(),
+ clnt_id.begin() + size)));
+}
+
+HWAddrPtr
+Dhcpv4SrvTest::generateHWAddr(size_t size /*= 6*/) {
+ const uint8_t hw_type = 123; // Just a fake number (typically 6=HTYPE_ETHER, see dhcp4.h)
+ OptionBuffer mac(size);
+ for (size_t i = 0; i < size; ++i) {
+ mac[i] = 50 + i;
+ }
+ return (HWAddrPtr(new HWAddr(mac, hw_type)));
+}
+
+OptionCustomPtr
+Dhcpv4SrvTest::makeServerIdOption(const IOAddress& address) {
+ OptionDefinitionPtr option_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_SERVER_IDENTIFIER);
+ OptionCustomPtr server_id(new OptionCustom(*option_def, Option::V4));
+ server_id->writeAddress(address);
+ return (server_id);
+}
+
+OptionPtr
+Dhcpv4SrvTest::makeFqdnListOption() {
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DOMAIN_SEARCH);
+
+ // Prepare buffer holding an array of FQDNs.
+ const uint8_t fqdn[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0
+ };
+
+ // Initialize a vector with the FQDN data.
+ std::vector<uint8_t> fqdn_buf(fqdn, fqdn + sizeof(fqdn));
+
+ OptionPtr option = def->optionFactory(Option::V4, DHO_DOMAIN_SEARCH,
+ fqdn_buf.begin(), fqdn_buf.end());
+
+ return (option);
+}
+
+void
+Dhcpv4SrvTest::checkAddressParams(const Pkt4Ptr& rsp,
+ const Subnet4Ptr subnet,
+ bool t1_present,
+ bool t2_present,
+ uint32_t expected_valid) {
+
+ // Technically inPool implies inRange, but let's be on the safe
+ // side and check both.
+ EXPECT_TRUE(subnet->inRange(rsp->getYiaddr()));
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, rsp->getYiaddr()));
+
+ // Check lease time
+ OptionUint32Ptr opt = boost::dynamic_pointer_cast<
+ OptionUint32>(rsp->getOption(DHO_DHCP_LEASE_TIME));
+ if (!opt) {
+ ADD_FAILURE() << "Lease time option missing in response or the"
+ " option has unexpected type";
+ } else if (subnet->getValid().getMin() != subnet->getValid().getMax()) {
+ EXPECT_GE(opt->getValue(), subnet->getValid().getMin());
+ EXPECT_LE(opt->getValue(), subnet->getValid().getMax());
+ } else {
+ EXPECT_EQ(opt->getValue(), subnet->getValid());
+ }
+
+ // Check expected value when wanted.
+ if (opt && expected_valid) {
+ EXPECT_EQ(opt->getValue(), expected_valid);
+ }
+
+ // Check T1 timer
+ opt = boost::dynamic_pointer_cast<
+ OptionUint32>(rsp->getOption(DHO_DHCP_RENEWAL_TIME));
+ if (t1_present) {
+ ASSERT_TRUE(opt) << "Required T1 option missing or it has"
+ " an unexpected type";
+ EXPECT_EQ(opt->getValue(), subnet->getT1());
+ } else {
+ EXPECT_FALSE(opt);
+ }
+
+ // Check T2 timer
+ opt = boost::dynamic_pointer_cast<
+ OptionUint32>(rsp->getOption(DHO_DHCP_REBINDING_TIME));
+ if (t2_present) {
+ ASSERT_TRUE(opt) << "Required T2 option missing or it has"
+ " an unexpected type";
+ EXPECT_EQ(opt->getValue(), subnet->getT2());
+ } else {
+ EXPECT_FALSE(opt);
+ }
+}
+
+void
+Dhcpv4SrvTest::checkResponse(const Pkt4Ptr& rsp, int expected_message_type,
+ uint32_t expected_transid) {
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(expected_message_type,
+ static_cast<int>(rsp->getType()));
+ EXPECT_EQ(expected_transid, rsp->getTransid());
+}
+
+Lease4Ptr
+Dhcpv4SrvTest::checkLease(const Pkt4Ptr& rsp, const OptionPtr& client_id,
+ const HWAddrPtr&, const IOAddress& expected_addr) {
+
+ ClientIdPtr id;
+ if (client_id) {
+ OptionBuffer data = client_id->getData();
+ id.reset(new ClientId(data));
+ }
+
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(expected_addr);
+ if (!lease) {
+ cout << "Lease for " << expected_addr
+ << " not found in the database backend.";
+ return (Lease4Ptr());
+ }
+
+ EXPECT_EQ(rsp->getYiaddr(), expected_addr);
+
+ EXPECT_EQ(expected_addr, lease->addr_);
+ if (client_id) {
+ EXPECT_TRUE(*lease->client_id_ == *id);
+ }
+ EXPECT_EQ(subnet_->getID(), lease->subnet_id_);
+
+ return (lease);
+}
+
+void
+Dhcpv4SrvTest::checkServerId(const Pkt4Ptr& rsp, const OptionPtr& expected_srvid) {
+ // Check that server included its server-id
+ OptionPtr opt = rsp->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+ ASSERT_TRUE(opt);
+ EXPECT_EQ(opt->getType(), expected_srvid->getType() );
+ EXPECT_EQ(opt->len(), expected_srvid->len() );
+ EXPECT_TRUE(opt->getData() == expected_srvid->getData());
+}
+
+void
+Dhcpv4SrvTest::checkClientId(const Pkt4Ptr& rsp, const OptionPtr& expected_clientid) {
+
+ bool include_clientid =
+ CfgMgr::instance().getCurrentCfg()->getEchoClientId();
+
+ // check that server included our own client-id
+ OptionPtr opt = rsp->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ if (include_clientid) {
+ // Normal mode: echo back (see RFC6842)
+ ASSERT_TRUE(opt);
+ EXPECT_EQ(expected_clientid->getType(), opt->getType());
+ EXPECT_EQ(expected_clientid->len(), opt->len());
+ EXPECT_TRUE(expected_clientid->getData() == opt->getData());
+ } else {
+ // Backward compatibility mode for pre-RFC6842 devices
+ ASSERT_FALSE(opt);
+ }
+}
+
+void
+Dhcpv4SrvTest::checkServerIdOption(const Pkt4Ptr& packet, const IOAddress& expected_address) {
+ OptionPtr opt = packet->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+ ASSERT_TRUE(opt) << "no server-id option";
+
+ OptionCustomPtr server_id_opt = boost::dynamic_pointer_cast<OptionCustom>(opt);
+ ASSERT_TRUE(server_id_opt) << "server-id option is not an instance of OptionCustom";
+
+ EXPECT_EQ(expected_address, server_id_opt->readAddress());
+}
+
+::testing::AssertionResult
+Dhcpv4SrvTest::createPacketFromBuffer(const Pkt4Ptr& src_pkt,
+ Pkt4Ptr& dst_pkt) {
+ // Create on-wire format of the packet. If pack() has been called
+ // on this instance of the packet already, the next call to pack()
+ // should remove all contents of the output buffer.
+ try {
+ src_pkt->pack();
+ } catch (const Exception& ex) {
+ return (::testing::AssertionFailure(::testing::Message()
+ << "Failed to parse source packet: "
+ << ex.what()));
+ }
+ // Get the output buffer from the source packet.
+ const util::OutputBuffer& buf = src_pkt->getBuffer();
+ // Create a copy of the packet using the output buffer from the source
+ // packet.
+ try {
+ dst_pkt.reset(new Pkt4(static_cast<const uint8_t*>(buf.getData()),
+ buf.getLength()));
+ } catch (const Exception& ex) {
+ return (::testing::AssertionFailure(::testing::Message()
+ << "Failed to create a"
+ " destination packet from"
+ " the buffer: "
+ << ex.what()));
+ }
+
+ // The dst_pkt unpack is performed on processPacket by the server.
+
+ return (::testing::AssertionSuccess());
+}
+
+void
+// cppcheck-suppress unusedFunction
+Dhcpv4SrvTest::TearDown() {
+
+ CfgMgr::instance().clear();
+
+ // Close all open sockets.
+ IfaceMgr::instance().closeSockets();
+
+ // Some unit tests override the default packet filtering class, used
+ // by the IfaceMgr. The dummy class, called PktFilterTest, reports the
+ // capability to directly respond to the clients without IP address
+ // assigned. This capability is not supported by the default packet
+ // filtering class: PktFilterInet. Therefore setting the dummy class
+ // allows to test scenarios, when server responds to the broadcast address
+ // on client's request, despite having support for direct response.
+ // The following call restores the use of original packet filtering class
+ // after the test.
+ try {
+ IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterInet()));
+
+ } catch (const Exception& ex) {
+ FAIL() << "Failed to restore the default (PktFilterInet) packet filtering"
+ << " class after the test. Exception has been caught: "
+ << ex.what();
+ }
+}
+
+void
+Dhcpv4SrvTest::testDiscoverRequest(const uint8_t msg_type) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Create an instance of the tested class.
+ boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
+
+ // Initialize the source HW address.
+ vector<uint8_t> mac(6);
+ for (uint8_t i = 0; i < 6; ++i) {
+ mac[i] = i * 10;
+ }
+ // Initialized the destination HW address.
+ vector<uint8_t> dst_mac(6);
+ for (uint8_t i = 0; i < 6; ++i) {
+ dst_mac[i] = i * 20;
+ }
+ // Create a DHCP message. It will be used to simulate the
+ // incoming message.
+ boost::shared_ptr<Pkt4> req(new Pkt4(msg_type, 1234));
+ // Create a response message. It will hold a response packet.
+ // Initially, set it to NULL.
+ boost::shared_ptr<Pkt4> rsp;
+ // Set the name of the interface on which packet is received.
+ req->setIface("eth0");
+ // Set the interface index.
+ req->setIndex(ETH0_INDEX);
+ // Set the target HW address. This value is normally used to
+ // construct the data link layer header.
+ req->setRemoteHWAddr(1, 6, dst_mac);
+ // Set the HW address. This value is set on DHCP level (in chaddr).
+ req->setHWAddr(1, 6, mac);
+ // Set local HW address. It is used to construct the data link layer
+ // header.
+ req->setLocalHWAddr(1, 6, mac);
+ // Set target IP address.
+ req->setRemoteAddr(IOAddress("192.0.2.55"));
+ // Set relay address and hops.
+ req->setGiaddr(IOAddress("192.0.2.10"));
+ req->setHops(1);
+
+ // We are going to test that certain options are returned
+ // in the response message when requested using 'Parameter
+ // Request List' option. Let's configure those options that
+ // are returned when requested.
+ configureRequestedOptions();
+
+ // Create a copy of the original packet by parsing its wire format.
+ // This simulates the real life scenario when we process the packet
+ // which was parsed from its wire format.
+ Pkt4Ptr received;
+ ASSERT_TRUE(createPacketFromBuffer(req, received));
+ received->unpack();
+ // Set interface. It is required for the server to generate server id.
+ received->setIface("eth0");
+ received->setIndex(ETH0_INDEX);
+ if (msg_type == DHCPDISCOVER) {
+ ASSERT_NO_THROW(rsp = srv->processDiscover(received));
+
+ // Should return OFFER
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(DHCPOFFER, rsp->getType());
+
+ } else {
+ ASSERT_NO_THROW(rsp = srv->processRequest(received));
+
+ // Should return ACK
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(DHCPACK, rsp->getType());
+
+ }
+
+ messageCheck(received, rsp);
+
+ // Basic options should be present when we got the lease.
+ EXPECT_TRUE(basicOptionsPresent(rsp));
+ // We did not request any options so these should not be present
+ // in the RSP.
+ EXPECT_TRUE(noRequestedOptions(rsp));
+
+ // Repeat the test but request some options.
+ // Add 'Parameter Request List' option.
+ addPrlOption(req);
+
+ ASSERT_TRUE(createPacketFromBuffer(req, received));
+ received->unpack();
+ ASSERT_TRUE(received->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST));
+
+ // Set interface. It is required for the server to generate server id.
+ received->setIface("eth0");
+ received->setIndex(ETH0_INDEX);
+
+ if (msg_type == DHCPDISCOVER) {
+ ASSERT_NO_THROW(rsp = srv->processDiscover(received));
+
+ // Should return non-NULL packet.
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(DHCPOFFER, rsp->getType());
+
+ } else {
+ ASSERT_NO_THROW(rsp = srv->processRequest(received));
+
+ // Should return non-NULL packet.
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(DHCPACK, rsp->getType());
+ }
+
+ // Check that the requested options are returned.
+ EXPECT_TRUE(basicOptionsPresent(rsp));
+ EXPECT_TRUE(requestedOptionsPresent(rsp));
+
+ // The following part of the test will test that the NAK is sent when
+ // there is no address pool configured. In the same time, we expect
+ // that the requested options are not included in NAK message, but that
+ // they are only included when yiaddr is set to non-zero value.
+ ASSERT_NO_THROW(subnet_->delPools(Lease::TYPE_V4));
+
+ // There has been a lease allocated for the particular client. So,
+ // even though we deleted the subnet, the client would get the
+ // existing lease (not a NAK). Therefore, we have to change the chaddr
+ // in the packet so as the existing lease is not returned.
+ req->setHWAddr(1, 6, std::vector<uint8_t>(2, 6));
+ ASSERT_TRUE(createPacketFromBuffer(req, received));
+ received->unpack();
+ ASSERT_TRUE(received->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST));
+
+ // Set interface. It is required for the server to generate server id.
+ received->setIface("eth0");
+ received->setIndex(ETH0_INDEX);
+
+ if (msg_type == DHCPDISCOVER) {
+ ASSERT_NO_THROW(rsp = srv->processDiscover(received));
+ // Should return NULL packet.
+ ASSERT_FALSE(rsp);
+
+ } else {
+ ASSERT_NO_THROW(rsp = srv->processRequest(received));
+ // Should return non-NULL packet.
+ ASSERT_TRUE(rsp);
+ // We should get the NAK packet with yiaddr set to 0.
+ EXPECT_EQ(DHCPNAK, rsp->getType());
+ ASSERT_EQ("0.0.0.0", rsp->getYiaddr().toText());
+
+ // Make sure that none of the requested options is returned in NAK.
+ // Also options such as Routers or Subnet Mask should not be there,
+ // because lease hasn't been acquired.
+ EXPECT_TRUE(noRequestedOptions(rsp));
+ EXPECT_TRUE(noBasicOptions(rsp));
+ }
+}
+
+void
+Dhcpv4SrvTest::buildCfgOptionTest(IOAddress expected_server_id,
+ Pkt4Ptr& query,
+ IOAddress requested,
+ IOAddress server_id) {
+ OptionDefinitionPtr req_addr_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_REQUESTED_ADDRESS);
+ ASSERT_TRUE(req_addr_def);
+
+ OptionDefinitionPtr sbnsel_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_SUBNET_SELECTION);
+ ASSERT_TRUE(sbnsel_def);
+
+ OptionCustomPtr req_addr(new OptionCustom(*req_addr_def, Option::V4));
+ req_addr->writeAddress(requested);
+
+ OptionCustomPtr sbnsel(new OptionCustom(*sbnsel_def, Option::V4));
+ sbnsel->writeAddress(requested);
+
+ query->addOption(req_addr);
+ query->addOption(sbnsel);
+ query->addOption(makeServerIdOption(server_id));
+
+ Pkt4Ptr response;
+ ASSERT_NO_THROW(response = srv_.processRequest(query));
+
+ checkServerIdOption(response, expected_server_id);
+
+ ASSERT_NO_THROW(query->delOption(DHO_DHCP_REQUESTED_ADDRESS));
+ ASSERT_NO_THROW(query->delOption(DHO_SUBNET_SELECTION));
+ ASSERT_NO_THROW(query->delOption(DHO_DHCP_SERVER_IDENTIFIER));
+}
+
+void
+Dhcpv4SrvTest::configure(const std::string& config,
+ const bool commit,
+ const bool open_sockets,
+ const bool create_managers,
+ const bool test) {
+ configure(config, srv_, commit, open_sockets, create_managers, test);
+}
+
+void
+Dhcpv4SrvTest::configure(const std::string& config,
+ NakedDhcpv4Srv& srv,
+ const bool commit,
+ const bool open_sockets,
+ const bool create_managers,
+ const bool test) {
+ 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 = configureDhcp4Server(srv, json, test));
+ ASSERT_TRUE(status);
+ int rcode;
+ ConstElementPtr comment = 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=4");
+ cfg_db->createManagers();
+ } );
+ }
+
+ 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().openSockets4();
+ }
+}
+
+std::pair<int, std::string>
+Dhcpv4SrvTest::configureWithStatus(const std::string& config, NakedDhcpv4Srv& srv,
+ const bool commit, const int exp_rcode) {
+ ConstElementPtr json;
+ try {
+ json = parseJSON(config);
+ } catch (const std::exception& ex){
+ // Fatal failure on parsing error
+
+ std::stringstream tmp;
+ tmp << "parsing failure:"
+ << "config:" << config << std::endl
+ << "error: " << ex.what();
+ ADD_FAILURE() << tmp.str();
+ return (std::make_pair(-1, tmp.str()));
+ }
+
+ ConstElementPtr status;
+
+ // Disable the re-detect flag
+ disableIfacesReDetect(json);
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ EXPECT_TRUE(status);
+ if (!status) {
+ return (make_pair(-1, "configureDhcp4Server didn't return anything"));
+ }
+
+ int rcode;
+ ConstElementPtr comment = config::parseAnswer(rcode, status);
+ EXPECT_EQ(exp_rcode, rcode) << comment->stringValue();
+
+ // Use specified lease database backend.
+ if (rcode == 0) {
+ EXPECT_NO_THROW( {
+ CfgDbAccessPtr cfg_db = CfgMgr::instance().getStagingCfg()->getCfgDbAccess();
+ cfg_db->setAppendedParameters("universe=4");
+ cfg_db->createManagers();
+ } );
+ if (commit) {
+ CfgMgr::instance().commit();
+ }
+ }
+
+ return (std::make_pair(rcode, comment->stringValue()));
+}
+
+Dhcpv4Exchange
+Dhcpv4SrvTest::createExchange(const Pkt4Ptr& query) {
+ bool drop = false;
+ Subnet4Ptr subnet = srv_.selectSubnet(query, drop);
+ EXPECT_FALSE(drop);
+ AllocEngine::ClientContext4Ptr context(new AllocEngine::ClientContext4());
+ EXPECT_TRUE(srv_.earlyGHRLookup(query, context));
+ Dhcpv4Exchange ex(srv_.alloc_engine_, query, context, subnet, drop);
+ EXPECT_FALSE(context);
+ EXPECT_FALSE(drop);
+ return (ex);
+}
+
+void
+Dhcpv4SrvTest::pretendReceivingPkt(NakedDhcpv4Srv& srv, const std::string& config,
+ uint8_t pkt_type, const std::string& stat_name) {
+
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Apply the configuration we just received.
+ configure(config);
+
+ // Let's just use one of the actual captured packets that we have.
+ Pkt4Ptr pkt = PktCaptures::captureRelayedDiscover();
+
+ // We just need to tweak it a it, to pretend that its type is as desired.
+ // Note that when receiving a packet, its on-wire form is stored in data_
+ // field. Most methods (including setType()) operates on option objects
+ // (objects stored in options_ after unpack() is called). Finally, outgoing
+ // packets are stored in out_buffer_. So we need to go through the full
+ // unpack/tweak/pack cycle and do repack, i.e. move the output buffer back
+ // to incoming buffer.
+ pkt->unpack();
+ pkt->setType(pkt_type); // Set message type.
+ pkt->pack();
+ pkt->data_.resize(pkt->getBuffer().getLength());
+ // Copy out_buffer_ to data_ to pretend that it's what was just received.
+ memcpy(&pkt->data_[0], pkt->getBuffer().getData(), pkt->getBuffer().getLength());
+ // Clear options so that they can be recreated on unpack.
+ pkt->options_.clear();
+
+ // Simulate that we have received that traffic
+ srv.fakeReceive(pkt);
+ srv.run();
+
+ using namespace isc::stats;
+ StatsMgr& mgr = StatsMgr::instance();
+ ObservationPtr pkt4_rcvd = mgr.getObservation("pkt4-received");
+ ObservationPtr tested_stat = mgr.getObservation(stat_name);
+
+ // All expected statistics must be present.
+ ASSERT_TRUE(pkt4_rcvd);
+ ASSERT_TRUE(tested_stat);
+
+ // They also must have expected values.
+ EXPECT_EQ(1, pkt4_rcvd->getInteger().first);
+ EXPECT_EQ(1, tested_stat->getInteger().first);
+}
+
+} // end of isc::dhcp::test namespace
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.h b/src/bin/dhcp4/tests/dhcp4_test_utils.h
new file mode 100644
index 0000000..17a3d5a
--- /dev/null
+++ b/src/bin/dhcp4/tests/dhcp4_test_utils.h
@@ -0,0 +1,786 @@
+// Copyright (C) 2013-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/.
+
+/// @file dhcp4_test_utils.h
+///
+/// @brief This file contains utility classes used for DHCPv4 server testing
+
+#ifndef DHCP4_TEST_UTILS_H
+#define DHCP4_TEST_UTILS_H
+
+#include <gtest/gtest.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt_filter.h>
+#include <dhcp/pkt_filter_inet.h>
+#include <dhcpsrv/alloc_engine.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcp4/dhcp4_srv.h>
+#include <dhcp4/parser_context.h>
+#include <asiolink/io_address.h>
+#include <cc/command_interpreter.h>
+#include <util/multi_threading_mgr.h>
+#include <list>
+
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Dummy Packet Filtering class.
+///
+/// This class reports capability to respond directly to the client which
+/// doesn't have address configured yet.
+///
+/// All packet and socket handling functions do nothing because they are not
+/// used in unit tests.
+class PktFilterTest : public PktFilter {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets the 'direct response' capability to true.
+ PktFilterTest()
+ : direct_resp_supported_(true) {
+ }
+
+ /// @brief Reports 'direct response' capability.
+ ///
+ /// @return always true.
+ virtual bool isDirectResponseSupported() const {
+ return (direct_resp_supported_);
+ }
+
+ /// Does nothing.
+ virtual SocketInfo openSocket(Iface&,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port, const bool, const bool) {
+ return (SocketInfo(addr, port, 0));
+ }
+
+ /// Does nothing.
+ virtual Pkt4Ptr receive(Iface&, const SocketInfo&) {
+ return Pkt4Ptr();
+ }
+
+ /// Does nothing.
+ virtual int send(const Iface&, uint16_t, const Pkt4Ptr&) {
+ return (0);
+ }
+
+ /// @brief Holds a boolean value which indicates whether direct response
+ /// capability is supported (true) or not (false).
+ bool direct_resp_supported_;
+
+};
+
+typedef boost::shared_ptr<PktFilterTest> PktFilterTestPtr;
+
+/// Forward definition for Dhcp4Client defined in dhcp4_client.h
+/// dhcp4_client.h includes dhcp_test_utils.h (this file), so to avoid
+/// circular dependencies, we need a forward class declaration.
+class Dhcp4Client;
+
+/// @brief "Naked" DHCPv4 server, exposes internal fields
+class NakedDhcpv4Srv: public Dhcpv4Srv {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor disables default modes of operation used by the
+ /// Dhcpv4Srv class:
+ /// - Send/receive broadcast messages through sockets on interfaces
+ /// which support broadcast traffic.
+ /// - Direct DHCPv4 traffic - communication with clients which do not
+ /// have IP address assigned yet.
+ ///
+ /// Enabling these modes requires root privileges so they must be
+ /// disabled for unit testing.
+ ///
+ /// Note, that disabling broadcast options on sockets does not impact
+ /// the operation of these tests because they use local loopback
+ /// interface which doesn't have broadcast capability anyway. It rather
+ /// prevents setting broadcast options on other (broadcast capable)
+ /// sockets which are opened on other interfaces in Dhcpv4Srv constructor.
+ ///
+ /// The Direct DHCPv4 Traffic capability can be disabled here because
+ /// it is tested with PktFilterLPFTest unittest. The tests which belong
+ /// to PktFilterLPFTest can be enabled on demand when root privileges can
+ /// be guaranteed.
+ ///
+ /// @param port port number to listen on; the default value 0 indicates
+ /// that sockets should not be opened.
+ NakedDhcpv4Srv(uint16_t port = 0)
+ : Dhcpv4Srv(port, false, false) {
+ // Create a default lease database backend.
+ std::string dbconfig = "type=memfile universe=4 persist=false";
+ isc::dhcp::LeaseMgrFactory::create(dbconfig);
+ // Create fixed server id.
+ server_id_.reset(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
+ asiolink::IOAddress("192.0.3.1")));
+ LeaseMgr::setIOService(getIOService());
+ }
+
+ /// @brief Returns fixed server identifier assigned to the naked server
+ /// instance.
+ OptionPtr getServerID() const {
+ return (server_id_);
+ }
+
+ /// @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 Pkt4Ptr receivePacket(int /*timeout*/) {
+
+ // If there is anything prepared as fake incoming traffic, use it
+ if (!fake_received_.empty()) {
+ Pkt4Ptr 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 (Pkt4Ptr());
+ }
+
+ /// @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 Pkt4Ptr& pkt) {
+ if (isc::util::MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ fake_sent_.push_back(pkt);
+ } else {
+ 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.
+ Pkt4Ptr receiveOneMsg() {
+ if (isc::util::MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ return (receiveOneMsgInternal());
+ } else {
+ return (receiveOneMsgInternal());
+ }
+ }
+
+ /// @brief fake receive packet from server
+ ///
+ /// The client uses this packet as a reply from the server.
+ /// This function should be called in a thread safe context.
+ ///
+ /// @return The received packet.
+ Pkt4Ptr receiveOneMsgInternal() {
+ // Return empty pointer if server hasn't responded.
+ if (fake_sent_.empty()) {
+ return (Pkt4Ptr());
+ }
+ Pkt4Ptr 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 Pkt4Ptr& pkt) {
+ fake_received_.push_back(pkt);
+ }
+
+ virtual ~NakedDhcpv4Srv() {
+ }
+
+ /// @brief Runs processing DHCPDISCOVER.
+ ///
+ /// @param discover a message received from client
+ /// @return DHCPOFFER message or null
+ Pkt4Ptr processDiscover(Pkt4Ptr& discover) {
+ AllocEngine::ClientContext4Ptr context(new AllocEngine::ClientContext4());
+ earlyGHRLookup(discover, context);
+ return (processDiscover(discover, context));
+ }
+
+ /// @brief Runs processing DHCPREQUEST.
+ ///
+ /// @param request a message received from client
+ /// @return DHCPACK or DHCPNAK message
+ Pkt4Ptr processRequest(Pkt4Ptr& request) {
+ AllocEngine::ClientContext4Ptr context(new AllocEngine::ClientContext4());
+ earlyGHRLookup(request, context);
+ return (processRequest(request, context));
+ }
+
+ /// @brief Runs processing DHCPRELEASE.
+ ///
+ /// @param release message received from client
+ void processRelease(Pkt4Ptr& release) {
+ AllocEngine::ClientContext4Ptr context(new AllocEngine::ClientContext4());
+ earlyGHRLookup(release, context);
+ processRelease(release, context);
+ }
+
+ /// @brief Runs processing DHCPDECLINE.
+ ///
+ /// @param decline message received from client
+ void processDecline(Pkt4Ptr& decline) {
+ AllocEngine::ClientContext4Ptr context(new AllocEngine::ClientContext4());
+ earlyGHRLookup(decline, context);
+ processDecline(decline, context);
+ }
+
+ /// @brief Runs processing DHCPINFORM.
+ ///
+ /// @param inform a message received from client
+ /// @return DHCPACK message
+ Pkt4Ptr processInform(Pkt4Ptr& inform) {
+ AllocEngine::ClientContext4Ptr context(new AllocEngine::ClientContext4());
+ earlyGHRLookup(inform, context);
+ return (processInform(inform, context));
+ }
+
+ /// @brief Dummy server identifier option used by various tests.
+ OptionPtr server_id_;
+
+ /// @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 NakedDhcpv4Srv::receivePacket() methods.
+ std::list<Pkt4Ptr> fake_received_;
+
+ /// @brief packets we pretend to send.
+ std::list<Pkt4Ptr> fake_sent_;
+
+ using Dhcpv4Srv::adjustIfaceData;
+ using Dhcpv4Srv::appendServerID;
+ using Dhcpv4Srv::processDiscover;
+ using Dhcpv4Srv::processRequest;
+ using Dhcpv4Srv::processRelease;
+ using Dhcpv4Srv::processDecline;
+ using Dhcpv4Srv::processInform;
+ using Dhcpv4Srv::processClientName;
+ using Dhcpv4Srv::createNameChangeRequests;
+ using Dhcpv4Srv::acceptServerId;
+ using Dhcpv4Srv::sanityCheck;
+ using Dhcpv4Srv::srvidToString;
+ using Dhcpv4Srv::classifyPacket;
+ using Dhcpv4Srv::deferredUnpack;
+ using Dhcpv4Srv::accept;
+ using Dhcpv4Srv::acceptMessageType;
+ using Dhcpv4Srv::selectSubnet;
+ using Dhcpv4Srv::setSendResponsesToSource;
+ using Dhcpv4Srv::VENDOR_CLASS_PREFIX;
+ using Dhcpv4Srv::shutdown_;
+ using Dhcpv4Srv::alloc_engine_;
+ using Dhcpv4Srv::server_port_;
+ using Dhcpv4Srv::client_port_;
+
+ /// @brief Mutex to protect the packet buffers.
+ std::mutex mutex_;
+};
+
+// We need to pass one reference to the Dhcp4Client, which is defined in
+// dhcp4_client.h. That header includes this file. To avoid circular
+// dependencies, we use forward declaration here.
+class Dhcp4Client;
+
+/// @brief Base class for DHCPv4 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 Constructor.
+ BaseServerTest();
+
+ /// @brief Destructor.
+ virtual ~BaseServerTest();
+
+private:
+
+ /// @brief Holds the original data directory.
+ std::string original_datadir_;
+
+};
+
+class Dhcpv4SrvTest : public BaseServerTest {
+public:
+
+ enum ExpectedResult {
+ SHOULD_PASS, // pass = accept decline, move lease to declined state.
+ SHOULD_FAIL // fail = reject the decline
+ };
+
+ class Dhcpv4SrvMTTestGuard {
+ public:
+ Dhcpv4SrvMTTestGuard(Dhcpv4SrvTest& test, bool mt_enabled) : test_(test) {
+ test_.setMultiThreading(mt_enabled);
+ }
+ ~Dhcpv4SrvMTTestGuard() {
+ test_.setMultiThreading(false);
+ }
+ Dhcpv4SrvTest& test_;
+ };
+
+ /// @brief Constructor
+ ///
+ /// Initializes common objects used in many tests.
+ /// Also sets up initial configuration in CfgMgr.
+ Dhcpv4SrvTest();
+
+ /// @brief Destructor
+ ///
+ /// Removes existing configuration.
+ virtual ~Dhcpv4SrvTest();
+
+ /// @brief Add 'Parameter Request List' option to the packet.
+ ///
+ /// This function adds PRL option comprising the following option codes:
+ /// - 5 - Name Server
+ /// - 15 - Domain Name
+ /// - 7 - Log Server
+ /// - 8 - Quotes Server
+ /// - 9 - LPR Server
+ ///
+ /// @param pkt packet to add PRL option to.
+ void addPrlOption(Pkt4Ptr& pkt);
+
+ /// @brief Configures options being requested in the PRL option.
+ ///
+ /// The lpr-servers option is NOT configured here although it is
+ /// added to the 'Parameter Request List' option in the
+ /// \ref addPrlOption. When requested option is not configured
+ /// the server should not return it in its response. The goal
+ /// of not configuring the requested option is to verify that
+ /// the server will not return it.
+ void configureRequestedOptions();
+
+ /// @brief Configures server identifier at different levels.
+ void configureServerIdentifier();
+
+ /// @brief checks that the response matches request
+ ///
+ /// @param q query (client's message)
+ /// @param a answer (server's message)
+ void messageCheck(const Pkt4Ptr& q, const Pkt4Ptr& a);
+
+ /// @brief Check that certain basic (always added when lease is acquired)
+ /// are present in a message.
+ ///
+ /// @param pkt A message to be checked.
+ /// @return Assertion result which indicates whether test passed or failed.
+ ::testing::AssertionResult basicOptionsPresent(const Pkt4Ptr& pkt);
+
+ /// @brief Check that certain basic (always added when lease is acquired)
+ /// are not present.
+ ///
+ /// @param pkt A packet to be checked.
+ /// @return Assertion result which indicates whether test passed or failed.
+ ::testing::AssertionResult noBasicOptions(const Pkt4Ptr& pkt);
+
+ /// @brief Check that certain requested options are present in the message.
+ ///
+ /// @param pkt A message to be checked.
+ /// @return Assertion result which indicates whether test passed or failed.
+ ::testing::AssertionResult requestedOptionsPresent(const Pkt4Ptr& pkt);
+
+ /// @brief Check that certain options (requested with PRL option)
+ /// are not present.
+ ///
+ /// @param pkt A packet to be checked.
+ /// @return Assertion result which indicates whether test passed or failed.
+ ::testing::AssertionResult noRequestedOptions(const Pkt4Ptr& pkt);
+
+ /// @brief generates client-id option
+ ///
+ /// Generate client-id option of specified length
+ /// Ids with different lengths are sufficient to generate
+ /// unique ids. If more fine grained control is required,
+ /// tests generate client-ids on their own.
+ /// Sets client_id_ field.
+ /// @param size size of the client-id to be generated
+ OptionPtr generateClientId(size_t size = 4);
+
+ /// @brief generate hardware address
+ ///
+ /// @param size size of the generated MAC address
+ /// @param pointer to Hardware Address object
+ HWAddrPtr generateHWAddr(size_t size = 6);
+
+ /// @brief Convenience method for making a server identifier option instance.
+ ///
+ /// @param address IP address to add to the option
+ ///
+ /// @return Pointer to the newly constructed option.
+ OptionCustomPtr makeServerIdOption(const isc::asiolink::IOAddress& address);
+
+ /// @brief Convenience method for making a fqdn list option instance.
+ ///
+ /// @return Pointer to the newly constructed option.
+ OptionPtr makeFqdnListOption();
+
+ /// Check that address was returned from proper range, that its lease
+ /// lifetime is correct, that T1 and T2 are returned properly
+ /// @param rsp response to be checked
+ /// @param subnet subnet that should be used to verify assigned address
+ /// and options
+ /// @param t1_present check that t1 must be present (true) or must not be
+ /// present (false)
+ /// @param t2_present check that t2 must be present (true) or must not be
+ /// present (false)
+ /// @param expected_valid check that lease lifetime has the not-zero
+ /// expected value (zero value means that do not check).
+ void checkAddressParams(const Pkt4Ptr& rsp, const Subnet4Ptr subnet,
+ bool t1_present = false,
+ bool t2_present = false,
+ uint32_t expected_valid = 0);
+
+ /// @brief Basic checks for generated response (message type and trans-id).
+ ///
+ /// @param rsp response packet to be validated
+ /// @param expected_message_type expected message type
+ /// @param expected_transid expected transaction-id
+ void checkResponse(const Pkt4Ptr& rsp, int expected_message_type,
+ uint32_t expected_transid);
+
+ /// @brief Checks if the lease sent to client is present in the database
+ ///
+ /// @param rsp response packet to be validated
+ /// @param client_id expected client-identifier (or NULL)
+ /// @param HWAddr expected hardware address (not used now)
+ /// @param expected_addr expected address
+ Lease4Ptr checkLease(const Pkt4Ptr& rsp, const OptionPtr& client_id,
+ const HWAddrPtr&,
+ const isc::asiolink::IOAddress& expected_addr);
+
+ /// @brief Checks if server response (OFFER, ACK, NAK) includes proper server-id
+ ///
+ /// @param rsp response packet to be validated
+ /// @param expected_srvid expected value of server-id
+ void checkServerId(const Pkt4Ptr& rsp, const OptionPtr& expected_srvid);
+
+ /// @brief Checks if server response (OFFER, ACK, NAK) includes proper client-id
+ ///
+ /// This method follows values reported by CfgMgr in echoClientId() method.
+ /// Depending on its configuration, the client-id is either mandatory or
+ /// forbidden to appear in the response.
+ ///
+ /// @param rsp response packet to be validated
+ /// @param expected_clientid expected value of client-id
+ void checkClientId(const Pkt4Ptr& rsp, const OptionPtr& expected_clientid);
+
+ /// @brief Checks the value of the dhcp-server-identifier option in a packet
+ ///
+ /// @param packet packet to test
+ /// @param expected_address IP address the packet's option should contain
+ void checkServerIdOption(const Pkt4Ptr& packet, const isc::asiolink::IOAddress& expected_address);
+
+ /// @brief Create packet from output buffer of another packet.
+ ///
+ /// This function creates a packet using an output buffer from another
+ /// packet. This imitates reception of a packet from the wire. The
+ /// unpack function is then called to parse packet contents and to
+ /// create a collection of the options carried by this packet.
+ ///
+ /// This function is useful for unit tests which verify that the received
+ /// packet is parsed correctly. In those cases it is usually inappropriate
+ /// to create an instance of the packet, add options, set packet
+ /// fields and use such packet as an input to processDiscover or
+ /// processRequest function. This is because, such a packet has certain
+ /// options already initialized and there is no way to verify that they
+ /// have been initialized when packet instance was created or wire buffer
+ /// processing. By creating a packet from the buffer we guarantee that the
+ /// new packet is entirely initialized during wire data parsing.
+ ///
+ /// @param src_pkt A source packet, to be copied.
+ /// @param [out] dst_pkt A destination packet.
+ ///
+ /// @return assertion result indicating if a function completed with
+ /// success or failure.
+ static ::testing::AssertionResult
+ createPacketFromBuffer(const isc::dhcp::Pkt4Ptr& src_pkt,
+ isc::dhcp::Pkt4Ptr& dst_pkt);
+
+ /// @brief Tests if Discover or Request message is processed correctly
+ ///
+ /// This test verifies that the Parameter Request List option is handled
+ /// correctly, i.e. it checks that certain options are present in the
+ /// server's response when they are requested and that they are not present
+ /// when they are not requested or NAK occurs.
+ ///
+ /// @todo We need an additional test for PRL option using real traffic
+ /// capture.
+ ///
+ /// @param msg_type DHCPDISCOVER or DHCPREQUEST
+ void testDiscoverRequest(const uint8_t msg_type);
+
+ /// @brief Create test which verifies server identifier.
+ ///
+ /// @param expected_server_id expected server identifier
+ /// @param query the query used to get associated client classes
+ /// @param requested the requested address
+ /// @param server_id server identifier
+ void buildCfgOptionTest(isc::asiolink::IOAddress expected_server_id,
+ Pkt4Ptr& query,
+ isc::asiolink::IOAddress requested,
+ isc::asiolink::IOAddress server_id);
+
+ /// @brief Runs DHCPv4 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.
+ void configure(const std::string& config,
+ const bool commit = true,
+ const bool open_sockets = true,
+ const bool create_managers = true,
+ const bool test = false);
+
+ /// @brief Configure specified DHCP server using JSON string.
+ ///
+ /// @param config String holding server configuration in JSON format.
+ /// @param srv Instance of the 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.
+ void configure(const std::string& config,
+ NakedDhcpv4Srv& srv,
+ const bool commit = true,
+ const bool open_sockets = true,
+ const bool create_managers = true,
+ const bool test = false);
+
+ /// @brief Configure specified DHCP server using JSON string.
+ ///
+ /// @param config String holding server configuration in JSON format.
+ /// @param srv Instance of the server to be configured.
+ /// @param commit A boolean flag indicating if the new configuration
+ /// should be committed (if true), or not (if false).
+ /// @param exp_rcode expected status code (default = 0 (success))
+ /// @return (a pair of status code and a string with result)
+ std::pair<int, std::string>
+ configureWithStatus(const std::string& config, NakedDhcpv4Srv& srv,
+ const bool commit = true, const int exp_rcode = 0);
+
+ /// @brief Pretends a packet of specified type was received.
+ ///
+ /// Instantiates fake network interfaces, configures passed Dhcpv4Srv,
+ /// then creates a message of specified type and sends it to the
+ /// server and then checks whether expected statistics were set
+ /// appropriately.
+ ///
+ /// @param srv the DHCPv4 server to be used
+ /// @param config JSON configuration to be used
+ /// @param pkt_type type of the packet to be faked
+ /// @param stat_name name of the expected statistic
+ void pretendReceivingPkt(NakedDhcpv4Srv& srv, const std::string& config,
+ uint8_t pkt_type, const std::string& stat_name);
+
+ /// @brief Create @c Dhcpv4Exchange from client's query.
+ Dhcpv4Exchange createExchange(const Pkt4Ptr& query);
+
+ /// @brief Performs 4-way exchange to obtain new lease.
+ ///
+ /// This is used as a preparatory step for Decline operation.
+ ///
+ /// @param client Client to be used to obtain a lease.
+ void acquireLease(Dhcp4Client& client);
+
+ /// @brief Tests if the acquired lease is or is not declined.
+ ///
+ /// @param client Dhcp4Client instance
+ /// @param hw_address_1 HW Address to be used to acquire the lease.
+ /// @param client_id_1 Client id to be used to acquire the lease.
+ /// @param hw_address_2 HW Address to be used to decline the lease.
+ /// @param client_id_2 Client id to be used to decline the lease.
+ /// @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.
+ void acquireAndDecline(Dhcp4Client& client,
+ const std::string& hw_address_1,
+ const std::string& client_id_1,
+ const std::string& hw_address_2,
+ const std::string& client_id_2,
+ ExpectedResult expected_result);
+
+ /// @brief Checks if received relay agent info option is echoed back to the
+ /// client.
+ void relayAgentInfoEcho();
+
+ /// @brief Checks if received bad relay agent info option is not echoed back
+ /// to the client.
+ void badRelayAgentInfoEcho();
+
+ /// @brief Checks if client port can be overridden in packets being sent.
+ void portsClientPort();
+
+ /// @brief Checks if server port can be overridden in packets being sent.
+ void portsServerPort();
+
+ /// @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);
+
+ /// @brief This function cleans up after the test.
+ virtual void TearDown();
+
+ /// @brief Set multi-threading mode.
+ void setMultiThreading(bool enabled) {
+ multi_threading_ = enabled;
+ }
+
+ /// @brief A subnet used in most tests.
+ Subnet4Ptr subnet_;
+
+ /// @brief A pool used in most tests.
+ Pool4Ptr pool_;
+
+ /// @brief A client-id used in most tests.
+ ClientIdPtr client_id_;
+
+ /// @brief Return code
+ int rcode_;
+
+ /// @brief Comment received from configuration.
+ isc::data::ConstElementPtr comment_;
+
+ /// @brief Server object under test.
+ NakedDhcpv4Srv 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::Parser4Context ctx;
+ return (ctx.parseString(in, isc::dhcp::Parser4Context::PARSER_JSON));
+}
+
+/// @brief Runs parser in Dhcp4 mode
+///
+/// This is a simplified Dhcp4 mode, so no outer { } and "Dhcp4" 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
+parseDHCP4(const std::string& in, bool verbose = false) {
+ try {
+ isc::dhcp::Parser4Context ctx;
+ isc::data::ElementPtr json;
+ json = ctx.parseString(in, isc::dhcp::Parser4Context::SUBPARSER_DHCP4);
+ 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::Parser4Context ctx;
+ return (ctx.parseString(in, isc::dhcp::Parser4Context::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 // DHCP4_TEST_UTILS_H
diff --git a/src/bin/dhcp4/tests/dhcp4_unittests.cc b/src/bin/dhcp4/tests/dhcp4_unittests.cc
new file mode 100644
index 0000000..3a7d4ec
--- /dev/null
+++ b/src/bin/dhcp4/tests/dhcp4_unittests.cc
@@ -0,0 +1,26 @@
+// Copyright (C) 2011-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 <dhcp4/dhcp4_log.h>
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+
+ ::testing::InitGoogleTest(&argc, argv);
+
+ // See the documentation of the KEA_* environment variables in
+ // src/lib/log/README for info on how to tweak logging
+ isc::log::initLogger();
+
+ setenv("KEA_PIDFILE_DIR", TEST_DATA_BUILDDIR, 1);
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/bin/dhcp4/tests/dhcp4to6_ipc_unittest.cc b/src/bin/dhcp4/tests/dhcp4to6_ipc_unittest.cc
new file mode 100644
index 0000000..2a47d71
--- /dev/null
+++ b/src/bin/dhcp4/tests/dhcp4to6_ipc_unittest.cc
@@ -0,0 +1,411 @@
+// 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/pkt4o6.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/dhcp4to6_ipc.h>
+#include <dhcp4/tests/dhcp4_test_utils.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 <gtest/gtest.h>
+#include <stdint.h>
+#include <utility>
+
+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 Dhcp4to6IpcTest : public Dhcpv4SrvTest {
+public:
+
+ /// @brief Constructor
+ ///
+ /// Configures IPC to use a test port. It also provides a fake
+ /// configuration of interfaces.
+ Dhcp4to6IpcTest()
+ : Dhcpv4SrvTest(),
+ iface_mgr_test_config_(true) {
+ IfaceMgr::instance().openSockets4();
+ configurePort(TEST_PORT);
+ // Install buffer4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().
+ registerCallout("buffer4_receive",
+ buffer4_receive_callout));
+ // Install buffer4_send_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().
+ registerCallout("buffer4_send", buffer4_send_callout));
+ // Verify we have a controlled server
+ ControlledDhcpv4Srv* srv = NULL;
+ EXPECT_NO_THROW(srv = ControlledDhcpv4Srv::getInstance());
+ EXPECT_TRUE(srv);
+ // Let's wipe all existing statistics.
+ StatsMgr::instance().removeAll();
+
+ // Set the flags to false as we expect them to be set in callouts.
+ callback_recv_pkt_options_copy_ = std::make_pair(false, false);
+ callback_sent_pkt_options_copy_ = std::make_pair(false, false);
+ }
+
+ /// @brief Destructor
+ ///
+ /// Various cleanups.
+ virtual ~Dhcp4to6IpcTest() {
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("buffer4_send");
+ callback_recv_pkt_.reset();
+ callback_sent_pkt_.reset();
+ bool status = HooksManager::unloadLibraries();
+ if (!status) {
+ cerr << "(fixture dtor) unloadLibraries failed" << endl;
+ }
+ }
+
+ /// @brief Configure DHCP4o6 port.
+ ///
+ /// @param port New port.
+ void configurePort(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 buffer4_receive hook
+ ///
+ /// This hook is at the beginning of processPacket
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int buffer4_receive_callout(CalloutHandle& callout_handle) {
+ callout_handle.getArgument("query4", callback_recv_pkt_);
+ Pkt4o6Ptr pkt4 = boost::dynamic_pointer_cast<Pkt4o6>(callback_recv_pkt_);
+ if (pkt4) {
+ callback_recv_pkt_options_copy_.first = pkt4->isCopyRetrievedOptions();
+ Pkt6Ptr pkt6 = pkt4->getPkt6();
+ if (pkt6) {
+ callback_recv_pkt_options_copy_.second =
+ pkt6->isCopyRetrievedOptions();
+ }
+ }
+ return (0);
+ }
+
+ /// @brief Handler for the buffer4_send hook
+ ///
+ /// This hook is at the end of the DHCPv4o6 packet handler
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int buffer4_send_callout(CalloutHandle& callout_handle) {
+ callout_handle.getArgument("response4", callback_sent_pkt_);
+ Pkt4o6Ptr pkt4 = boost::dynamic_pointer_cast<Pkt4o6>(callback_sent_pkt_);
+ if (pkt4) {
+ callback_sent_pkt_options_copy_.first = pkt4->isCopyRetrievedOptions();
+ Pkt6Ptr pkt6 = pkt4->getPkt6();
+ if (pkt6) {
+ callback_sent_pkt_options_copy_.second =
+ pkt6->isCopyRetrievedOptions();
+ }
+ }
+ return (0);
+ }
+
+ /// @brief Response Pkt4 shared pointer returned in the receive callout
+ static Pkt4Ptr callback_recv_pkt_;
+
+ /// @brief Response Pkt4 shared pointer returned in the send callout
+ static Pkt4Ptr callback_sent_pkt_;
+
+ /// Flags indicating if copying retrieved options was enabled for
+ /// a received packet during callout execution.
+ static std::pair<bool, bool> callback_recv_pkt_options_copy_;
+
+ /// Flags indicating if copying retrieved options was enabled for
+ /// a sent packet during callout execution.
+ static std::pair<bool, bool> callback_sent_pkt_options_copy_;
+
+ /// @brief reference to a controlled server
+ ///
+ /// Dhcp4to6Ipc::handler() uses the instance of the controlled server
+ /// so it has to be build. This reference does this.
+ ControlledDhcpv4Srv srv_;
+
+private:
+
+ /// @brief Provides fake configuration of interfaces.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+
+};
+
+Pkt4Ptr Dhcp4to6IpcTest::callback_recv_pkt_;
+Pkt4Ptr Dhcp4to6IpcTest::callback_sent_pkt_;
+std::pair<bool, bool> Dhcp4to6IpcTest::callback_recv_pkt_options_copy_;
+std::pair<bool, bool> Dhcp4to6IpcTest::callback_sent_pkt_options_copy_;
+
+void
+Dhcp4to6IpcTest::configurePort(uint16_t port) {
+ CfgMgr::instance().getStagingCfg()->setDhcp4o6Port(port);
+}
+
+OptionPtr
+Dhcp4to6IpcTest::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(Dhcp4to6IpcTest, invalidPortError) {
+ // Create instance of the IPC endpoint under test with out-of-range port.
+ configurePort(65535);
+ Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance();
+ EXPECT_THROW(ipc.open(), isc::OutOfRange);
+}
+
+// This test verifies that the DHCPv4 endpoint of the DHCPv4o6 IPC can
+// receive messages.
+TEST_F(Dhcp4to6IpcTest, receive) {
+ // Create instance of the IPC endpoint under test.
+ Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance();
+ // Create instance of the IPC endpoint being used as a source of messages.
+ TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V6);
+
+ // 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_QUERY, 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 received packet
+ Dhcp4to6IpcTest::callback_recv_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 message has been received.
+ // The buffer4_receive hook is at the beginning of processPacket
+ // so this proves it was passed to it.
+ Pkt4Ptr pkt4_received = Dhcp4to6IpcTest::callback_recv_pkt_;
+ ASSERT_TRUE(pkt4_received);
+ Pkt4o6Ptr pkt_received =
+ boost::dynamic_pointer_cast<Pkt4o6>(pkt4_received);
+ ASSERT_TRUE(pkt_received);
+ Pkt6Ptr pkt6_received = pkt_received->getPkt6();
+ ASSERT_TRUE(pkt6_received);
+ EXPECT_EQ("eth0", pkt6_received->getIface());
+ EXPECT_EQ(ETH0_INDEX, pkt6_received->getIndex());
+ EXPECT_EQ("2001:db8:1::123", pkt6_received->getRemoteAddr().toText());
+
+ // Both DHCP4o6 and encapsulated DHCPv6 packet should have the
+ // flag enabled.
+ EXPECT_TRUE(callback_recv_pkt_options_copy_.first);
+ EXPECT_TRUE(callback_recv_pkt_options_copy_.second);
+}
+
+// This test verifies that message with multiple DHCPv4 query options
+// is rejected.
+TEST_F(Dhcp4to6IpcTest, receiveMultipleQueries) {
+ // Create instance of the IPC endpoint under test.
+ Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance();
+ // Create instance of the IPC endpoint being used as a source of messages.
+ TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V6);
+
+ // 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_QUERY, 1234));
+ // Add two DHCPv4 query options.
+ pkt->addOption(createDHCPv4MsgOption());
+ pkt->addOption(createDHCPv4MsgOption());
+ pkt->setIface("eth0");
+ pkt->setIndex(ETH0_INDEX);
+ pkt->setRemoteAddr(IOAddress("2001:db8:1::123"));
+ ASSERT_NO_THROW(pkt->pack());
+
+ // Reset the received packet
+ Dhcp4to6IpcTest::callback_recv_pkt_.reset();
+
+ // Send and wait up to 1 second to receive it.
+ ASSERT_NO_THROW(src_ipc.send(pkt));
+ EXPECT_NO_THROW(IfaceMgr::instance().receive6(1, 0));
+
+ // No message should has been sent.
+ Pkt4Ptr pkt4_received = Dhcp4to6IpcTest::callback_recv_pkt_;
+ EXPECT_FALSE(pkt4_received);
+}
+
+// This test verifies that message with no DHCPv4 query options is rejected.
+TEST_F(Dhcp4to6IpcTest, receiveNoQueries) {
+ // Create instance of the IPC endpoint under test.
+ Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance();
+ // Create instance of the IPC endpoint being used as a source of messages.
+ TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V6);
+
+ // Open both endpoints.
+ ASSERT_NO_THROW(ipc.open());
+ ASSERT_NO_THROW(src_ipc.open());
+
+ // Create message to be sent over IPC without DHCPv4 query option.
+ Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 1234));
+ pkt->setIface("eth0");
+ pkt->setIndex(ETH0_INDEX);
+ pkt->setRemoteAddr(IOAddress("2001:db8:1::123"));
+ ASSERT_NO_THROW(pkt->pack());
+
+ // Reset the received packet
+ Dhcp4to6IpcTest::callback_recv_pkt_.reset();
+
+ // Send and wait up to 1 second to receive it.
+ ASSERT_NO_THROW(src_ipc.send(pkt));
+ EXPECT_NO_THROW(IfaceMgr::instance().receive6(1, 0));
+
+ // No message should has been sent.
+ Pkt4Ptr pkt4_received = Dhcp4to6IpcTest::callback_recv_pkt_;
+ EXPECT_FALSE(pkt4_received);
+}
+
+// This test verifies that the DHCPv4 endpoint of the DHCPv4o6 IPC can
+// process messages.
+TEST_F(Dhcp4to6IpcTest, process) {
+ // Create instance of the IPC endpoint under test.
+ Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance();
+ // Create instance of the IPC endpoint being used as a source of messages.
+ TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V6);
+
+ // Open both endpoints.
+ ASSERT_NO_THROW(ipc.open());
+ ASSERT_NO_THROW(src_ipc.open());
+
+ // Get statistics
+ StatsMgr& mgr = StatsMgr::instance();
+ ObservationPtr pkt4_snd = mgr.getObservation("pkt4-sent");
+ ObservationPtr pkt4_ack = mgr.getObservation("pkt4-ack-sent");
+ EXPECT_FALSE(pkt4_snd);
+ EXPECT_FALSE(pkt4_ack);
+
+ // Create an information request message
+ Pkt4Ptr infreq(new Pkt4(DHCPINFORM, 1234));
+ infreq->setHWAddr(generateHWAddr(6));
+ infreq->setCiaddr(IOAddress("192.0.1.2"));
+ // Make a wire representation of the DHCPv4 message.
+ infreq->pack();
+ OutputBuffer& output_buffer = infreq->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));
+
+ // Create message to be sent over IPC.
+ Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 1234));
+ pkt->addOption(opt_msg);
+ pkt->setIface("eth0");
+ pkt->setIndex(ETH0_INDEX);
+ pkt->setRemoteAddr(IOAddress("2001:db8:1::123"));
+ ASSERT_NO_THROW(pkt->pack());
+
+ // Reset the received packet
+ Dhcp4to6IpcTest::callback_recv_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 message has been received.
+ Pkt4Ptr pkt4_received = Dhcp4to6IpcTest::callback_recv_pkt_;
+ ASSERT_TRUE(pkt4_received);
+ Pkt4o6Ptr pkt_received =
+ boost::dynamic_pointer_cast<Pkt4o6>(pkt4_received);
+ ASSERT_TRUE(pkt_received);
+ Pkt6Ptr pkt6_received = pkt_received->getPkt6();
+ ASSERT_TRUE(pkt6_received);
+ EXPECT_EQ("eth0", pkt6_received->getIface());
+ EXPECT_EQ(ETH0_INDEX, pkt6_received->getIndex());
+ EXPECT_EQ("2001:db8:1::123", pkt6_received->getRemoteAddr().toText());
+
+ // Make sure that the message has been processed.
+ // Using the buffer4_send hook
+ Pkt4Ptr pkt4_sent = Dhcp4to6IpcTest::callback_sent_pkt_;
+ ASSERT_TRUE(pkt4_sent);
+ EXPECT_EQ(DHCPACK, pkt4_sent->getType());
+ Pkt4o6Ptr pkt_sent = boost::dynamic_pointer_cast<Pkt4o6>(pkt4_sent);
+ ASSERT_TRUE(pkt_sent);
+ Pkt6Ptr pkt6_sent = pkt_sent->getPkt6();
+ ASSERT_TRUE(pkt6_sent);
+ EXPECT_EQ(DHCPV6_DHCPV4_RESPONSE, pkt6_sent->getType());
+ EXPECT_EQ("eth0", pkt6_sent->getIface());
+ EXPECT_EQ(ETH0_INDEX, pkt6_sent->getIndex());
+ EXPECT_EQ("2001:db8:1::123", pkt6_sent->getRemoteAddr().toText());
+
+ // Both DHCP4o6 and encapsulated DHCPv6 packet should have the
+ // flag enabled.
+ EXPECT_TRUE(callback_sent_pkt_options_copy_.first);
+ EXPECT_TRUE(callback_sent_pkt_options_copy_.second);
+
+ // Verify the 4o6 part
+ OptionCollection sent_msgs = pkt6_sent->getOptions(D6O_DHCPV4_MSG);
+ ASSERT_EQ(1, sent_msgs.size());
+ OptionPtr sent_msg = sent_msgs.begin()->second;
+ ASSERT_TRUE(sent_msg);
+ const OptionBuffer sent_buf = sent_msg->getData();
+ Pkt4Ptr pkt4_opt;
+ ASSERT_NO_THROW(pkt4_opt.reset(new Pkt4(&sent_buf[0], sent_buf.size())));
+ ASSERT_NO_THROW(pkt4_opt->unpack());
+ EXPECT_EQ(DHCPACK, pkt4_sent->getType());
+ EXPECT_EQ(pkt4_sent->len(), pkt4_opt->len());
+
+ // Verify statistics
+ pkt4_snd = mgr.getObservation("pkt4-sent");
+ pkt4_ack = mgr.getObservation("pkt4-ack-sent");
+ ASSERT_TRUE(pkt4_snd);
+ ASSERT_TRUE(pkt4_ack);
+ EXPECT_EQ(1, pkt4_snd->getInteger().first);
+ EXPECT_EQ(1, pkt4_ack->getInteger().first);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/direct_client_unittest.cc b/src/bin/dhcp4/tests/direct_client_unittest.cc
new file mode 100644
index 0000000..3d98e4e
--- /dev/null
+++ b/src/bin/dhcp4/tests/direct_client_unittest.cc
@@ -0,0 +1,435 @@
+// 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 <dhcp/iface_mgr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/classify.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_subnets4.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcp4/json_config_parser.h>
+#include <dhcp4/tests/dhcp4_client.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Test fixture class for testing message processing from directly
+/// connected clients.
+///
+/// This class provides mechanisms for testing processing of DHCPv4 messages
+/// from directly connected clients.
+class DirectClientTest : public Dhcpv4SrvTest {
+public:
+ /// @brief Constructor.
+ ///
+ /// Initializes DHCPv4 server object used by various tests.
+ DirectClientTest();
+
+ /// @brief Configures the server with one subnet.
+ ///
+ /// This creates new configuration for the DHCPv4 with one subnet having
+ /// a specified prefix.
+ ///
+ /// The subnet parameters (such as options, timers etc.) are arbitrarily
+ /// selected. The subnet and pool mask is always /24. The real configuration
+ /// would exclude .0 (network address) and .255 (broadcast address), but we
+ /// ignore that fact for the sake of test simplicity.
+ ///
+ /// @param prefix Prefix for a subnet.
+ void configureSubnet(const std::string& prefix);
+
+ /// @brief Configures the server with two subnets.
+ ///
+ /// This function configures DHCPv4 server with two different subnets.
+ /// The subnet parameters (such as options, timers etc.) are arbitrarily
+ /// selected. The subnet and pool mask is /24. The real configuration
+ /// would exclude .0 (network address) and .255 (broadcast address), but we
+ /// ignore that fact for the sake of test simplicity.
+ ///
+ /// @param prefix1 Prefix of the first subnet to be configured.
+ /// @param prefix2 Prefix of the second subnet to be configured.
+ void configureTwoSubnets(const std::string& prefix1,
+ const std::string& prefix2);
+
+ /// @brief Creates simple message from a client.
+ ///
+ /// This function creates a DHCPv4 message having a specified type
+ /// (e.g. Discover, Request) and sets some properties of this
+ /// message: client identifier, address and interface. The copy of
+ /// this message is then created by parsing wire data of the original
+ /// message. This simulates the case when the message is received and
+ /// parsed by the server.
+ ///
+ /// @param msg_type Type of the message to be created.
+ /// @param iface Name of the interface on which the message has been
+ /// "received" by the server.
+ /// @param ifindex Index of the interface on which the message has been
+ /// "received" by the server.
+ ///
+ /// @return Generated message.
+ Pkt4Ptr createClientMessage(const uint16_t msg_type,
+ const std::string& iface,
+ const uint32_t ifindex);
+
+ /// @brief Creates simple message from a client.
+ ///
+ /// This function configures a client's message by adding client identifier,
+ /// setting interface and addresses. The copy of this message is then
+ /// created by parsing wire data of the original message. This simulates the
+ /// case when the message is received and parsed by the server.
+ ///
+ /// @param msg Caller supplied message to be configured. This object must
+ /// not be NULL.
+ /// @param iface Name of the interface on which the message has been
+ /// "received" by the server.
+ /// @param ifindex Index of the interface on which the message has been
+ /// "received" by the server.
+ ///
+ /// @return Configured and parsed message.
+ Pkt4Ptr createClientMessage(const Pkt4Ptr &msg,
+ const std::string& iface,
+ const uint32_t ifindex);
+
+ /// @brief This test checks that the message from directly connected client
+ /// is processed and that client is offered IPv4 address from the subnet
+ /// which is suitable for the local interface on which the client's message
+ /// is received. This test uses two subnets, with two active interfaces
+ /// which IP addresses belong to these subnets. The address offered to the
+ /// client which message has been sent over eth0 should belong to a
+ /// different subnet than the address offered for the client sending its
+ /// message via eth1.
+ void twoSubnets();
+
+ /// @brief This test checks that server selects a subnet when receives a
+ /// message through an interface for which the subnet has been configured.
+ /// This interface has IPv4 address assigned which belongs to this subnet.
+ /// This test also verifies that when the message is received through the
+ /// interface for which there is no suitable subnet, the message is
+ /// discarded.
+ void oneSubnet();
+
+ /// @brief This test verifies that the server uses ciaddr to select a subnet
+ /// for a client which renews its lease.
+ void renew();
+
+ /// This test verifies that when a client in the Rebinding state broadcasts
+ /// a Request message through an interface for which a subnet is configured,
+ /// the server responds to this Request. It also verifies that when such a
+ /// Request is sent through the interface for which there is no subnet
+ /// configured the client's message is discarded.
+ void rebind();
+
+ /// @brief classes the client belongs to
+ ///
+ /// This is empty in most cases, but it is needed as a parameter for all
+ /// getSubnet4() calls.
+ ClientClasses classify_;
+};
+
+DirectClientTest::DirectClientTest() : Dhcpv4SrvTest() {
+}
+
+void
+DirectClientTest::configureSubnet(const std::string& prefix) {
+ std::ostringstream config;
+ config << "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"option-data\": [ ],"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"" << prefix << "/24\" } ],"
+ " \"subnet\": \"" << prefix << "/24\", "
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"valid-lifetime\": 4000"
+ "} ],"
+ "\"valid-lifetime\": 4000 }";
+
+ configure(config.str());
+}
+
+void
+DirectClientTest::configureTwoSubnets(const std::string& prefix1,
+ const std::string& prefix2) {
+ std::ostringstream config;
+ config << "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"option-data\": [ ],"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"" << prefix1 << "/24\" } ],"
+ " \"subnet\": \"" << prefix1 << "/24\", "
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"valid-lifetime\": 4000"
+ " },"
+ "{ "
+ " \"pools\": [ { \"pool\": \"" << prefix2 << "/24\" } ],"
+ " \"subnet\": \"" << prefix2 << "/24\", "
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"valid-lifetime\": 4000"
+ "} ],"
+ "\"valid-lifetime\": 4000 }";
+
+ configure(config.str());
+}
+
+Pkt4Ptr
+DirectClientTest::createClientMessage(const uint16_t msg_type,
+ const std::string& iface,
+ const uint32_t ifindex) {
+ // Create a source packet.
+ Pkt4Ptr msg = Pkt4Ptr(new Pkt4(msg_type, 1234));
+ return (createClientMessage(msg, iface, ifindex));
+
+}
+
+Pkt4Ptr
+DirectClientTest::createClientMessage(const Pkt4Ptr& msg,
+ const std::string& iface,
+ const uint32_t ifindex) {
+ msg->setRemoteAddr(IOAddress("255.255.255.255"));
+ msg->addOption(generateClientId());
+ msg->setIface(iface);
+ msg->setIndex(ifindex);
+
+ // Create copy of this packet by parsing its wire data. Make sure that the
+ // local and remote address are set like it was a message sent from the
+ // directly connected client.
+ Pkt4Ptr received;
+ createPacketFromBuffer(msg, received);
+ received->setIface(iface);
+ received->setIndex(ifindex);
+ received->setLocalAddr(IOAddress("255.255.255.255"));
+ received->setRemoteAddr(IOAddress("0.0.0.0"));
+
+ return (received);
+}
+
+void
+DirectClientTest::twoSubnets() {
+ // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
+ IfaceMgrTestConfig iface_config(true);
+ // After creating interfaces we have to open sockets as it is required
+ // by the message processing code.
+ ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
+ // Add two subnets: address on eth0 belongs to the second subnet,
+ // address on eth1 belongs to the first subnet.
+ ASSERT_NO_FATAL_FAILURE(configureTwoSubnets("192.0.2.0", "10.0.0.0"));
+ // Create Discover and simulate reception of this message through eth0.
+ Pkt4Ptr dis = createClientMessage(DHCPDISCOVER, "eth0", ETH0_INDEX);
+ srv_.fakeReceive(dis);
+ // Create Request and simulate reception of this message through eth1.
+ Pkt4Ptr req = createClientMessage(DHCPREQUEST, "eth1", ETH1_INDEX);
+ srv_.fakeReceive(req);
+
+ // Process clients' messages.
+ srv_.run();
+
+ // Check that the server did send responses.
+ ASSERT_EQ(2, srv_.fake_sent_.size());
+
+ // In multi-threading responses can be received out of order.
+ Pkt4Ptr offer;
+ Pkt4Ptr ack;
+
+ while (srv_.fake_sent_.size()) {
+ // Make sure that we received a response.
+ Pkt4Ptr response = srv_.fake_sent_.front();
+ ASSERT_TRUE(response);
+ srv_.fake_sent_.pop_front();
+
+ if (response->getType() == DHCPOFFER) {
+ offer = response;
+ } else if (response->getType() == DHCPACK) {
+ ack = response;
+ }
+ }
+
+ // Client should get an Offer (not a NAK).
+ ASSERT_TRUE(offer);
+
+ // Client should get an Ack (not a NAK).
+ ASSERT_TRUE(ack);
+
+ // Check that the offered address belongs to the suitable subnet.
+ Subnet4Ptr subnet = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets4()->selectSubnet(offer->getYiaddr());
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
+
+
+ // Check that the offered address belongs to the suitable subnet.
+ subnet = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets4()->selectSubnet(ack->getYiaddr());
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ("192.0.2.0", subnet->get().first.toText());
+}
+
+TEST_F(DirectClientTest, twoSubnets) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ twoSubnets();
+}
+
+TEST_F(DirectClientTest, twoSubnetsMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ twoSubnets();
+}
+
+void
+DirectClientTest::oneSubnet() {
+ // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
+ IfaceMgrTestConfig iface_config(true);
+ // After creating interfaces we have to open sockets as it is required
+ // by the message processing code.
+ ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
+ // Add a subnet which will be selected when a message from directly
+ // connected client is received through interface eth0.
+ ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
+ // Create Discover and simulate reception of this message through eth0.
+ Pkt4Ptr dis = createClientMessage(DHCPDISCOVER, "eth0", ETH0_INDEX);
+ srv_.fakeReceive(dis);
+ // Create Request and simulate reception of this message through eth1.
+ Pkt4Ptr req = createClientMessage(DHCPDISCOVER, "eth1", ETH1_INDEX);
+ srv_.fakeReceive(req);
+
+ // Process clients' messages.
+ srv_.run();
+
+ // Check that the server sent one response for the message received
+ // through eth0. The other client's message should be discarded.
+ ASSERT_EQ(1, srv_.fake_sent_.size());
+
+ // Check the response. The first Discover was sent via eth0 for which
+ // the subnet has been configured.
+ Pkt4Ptr response = srv_.fake_sent_.front();
+ ASSERT_TRUE(response);
+ srv_.fake_sent_.pop_front();
+
+ // Since Discover has been received through the interface for which
+ // the subnet has been configured, the server should respond with
+ // an Offer message.
+ ASSERT_EQ(DHCPOFFER, response->getType());
+ // Check that the offered address belongs to the suitable subnet.
+ Subnet4Ptr subnet = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets4()->selectSubnet(response->getYiaddr());
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
+}
+
+TEST_F(DirectClientTest, oneSubnet) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ oneSubnet();
+}
+
+TEST_F(DirectClientTest, oneSubnetMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ oneSubnet();
+}
+
+void
+DirectClientTest::renew() {
+ // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
+ IfaceMgrTestConfig iface_config(true);
+ // After creating interfaces we have to open sockets as it is required
+ // by the message processing code.
+ ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
+ // Add a subnet.
+ ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
+
+ // Create the DHCPv4 client.
+ Dhcp4Client client;
+ client.useRelay(false);
+
+ // Obtain the lease using the 4-way exchange.
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("10.0.0.10"))));
+ ASSERT_EQ("10.0.0.10", client.config_.lease_.addr_.toText());
+
+ // Put the client into the renewing state.
+ client.setState(Dhcp4Client::RENEWING);
+
+ // Renew, and make sure we have obtained the same address.
+ ASSERT_NO_THROW(client.doRequest());
+ ASSERT_TRUE(client.getContext().response_);
+ EXPECT_EQ(DHCPACK, static_cast<int>(client.getContext().response_->getType()));
+ EXPECT_EQ("10.0.0.10", client.config_.lease_.addr_.toText());
+}
+
+TEST_F(DirectClientTest, renew) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ renew();
+}
+
+TEST_F(DirectClientTest, renewMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ renew();
+}
+
+void
+DirectClientTest::rebind() {
+ // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
+ IfaceMgrTestConfig iface_config(true);
+ // After creating interfaces we have to open sockets as it is required
+ // by the message processing code.
+ ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
+ // Add a subnet.
+ ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
+
+ // Create the DHCPv4 client.
+ Dhcp4Client client;
+ client.useRelay(false);
+
+ // Obtain the lease using the 4-way exchange.
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("10.0.0.10"))));
+ ASSERT_EQ("10.0.0.10", client.config_.lease_.addr_.toText());
+
+ // Put the client into the rebinding state.
+ client.setState(Dhcp4Client::REBINDING);
+
+ // Broadcast Request through an interface for which there is no subnet
+ // configured. This message should be discarded by the server.
+ client.setIfaceName("eth1");
+ client.setIfaceIndex(ETH1_INDEX);
+ ASSERT_NO_THROW(client.doRequest());
+ EXPECT_FALSE(client.getContext().response_);
+
+ // Send Rebind over the correct interface, and make sure we have obtained
+ // the same address.
+ client.setIfaceName("eth0");
+ client.setIfaceIndex(ETH0_INDEX);
+ ASSERT_NO_THROW(client.doRequest());
+ ASSERT_TRUE(client.getContext().response_);
+ EXPECT_EQ(DHCPACK, static_cast<int>(client.getContext().response_->getType()));
+ EXPECT_EQ("10.0.0.10", client.config_.lease_.addr_.toText());
+}
+
+TEST_F(DirectClientTest, rebind) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ rebind();
+}
+
+TEST_F(DirectClientTest, rebindMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ rebind();
+}
+
+}
diff --git a/src/bin/dhcp4/tests/dora_unittest.cc b/src/bin/dhcp4/tests/dora_unittest.cc
new file mode 100644
index 0000000..3af9acb
--- /dev/null
+++ b/src/bin/dhcp4/tests/dora_unittest.cc
@@ -0,0 +1,2792 @@
+// Copyright (C) 2014-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 <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/subnet_id.h>
+#include <testutils/gtest_utils.h>
+
+#ifdef HAVE_MYSQL
+#include <mysql/testutils/mysql_schema.h>
+#endif
+
+#ifdef HAVE_PGSQL
+#include <pgsql/testutils/pgsql_schema.h>
+#endif
+
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp4/tests/dhcp4_client.h>
+#include <boost/shared_ptr.hpp>
+#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 DORA tests.
+///
+/// - Configuration 0:
+/// - Used for testing direct traffic
+/// - 1 subnet: 10.0.0.0/24
+/// - 1 pool: 10.0.0.10-10.0.0.100
+/// - Router option present: 10.0.0.200 and 10.0.0.201
+/// - Domain Name Server option present: 10.0.0.202, 10.0.0.203.
+/// - Log Servers option present: 192.0.2.200 and 192.0.2.201
+/// - Quotes Servers option present: 192.0.2.202, 192.0.2.203.
+///
+/// - Configuration 1:
+/// - Use for testing relayed messages
+/// - 1 subnet: 192.0.2.0/24
+/// - Router option present: 192.0.2.200 and 192.0.2.201
+/// - Domain Name Server option present: 192.0.2.202, 192.0.2.203.
+/// - Log Servers option present: 192.0.2.200 and 192.0.2.201
+/// - Quotes Servers option present: 192.0.2.202, 192.0.2.203.
+///
+/// - Configuration 2:
+/// - Use for testing simple scenarios with host reservations
+/// - 1 subnet: 10.0.0.0/24
+/// - One reservation for the client using MAC address:
+/// aa:bb:cc:dd:ee:ff, reserved address 10.0.0.7
+///
+/// - Configuration 3:
+/// - Use for testing match-client-id flag
+/// - 1 subnet: 10.0.0.0/24
+/// - 1 pool: 10.0.0.10-10.0.0.100
+/// - match-client-id flag is set to false, thus the server
+/// uses HW address for lease lookup, rather than client id
+///
+/// - Configuration 4:
+/// - Used for testing host reservations where circuit-id takes precedence
+/// over hw-address, and the hw-address takes precedence over duid.
+/// - 1 subnet: 10.0.0.0/24
+/// - 3 reservations for this subnet:
+/// - IP address 10.0.0.7 for HW address aa:bb:cc:dd:ee:ff
+/// - IP address 10.0.0.8 for DUID 01:02:03:04:05
+/// - IP address 10.0.0.9 for circuit-id 'charter950'
+/// - IP address 10.0.0.1 for client-id
+///
+/// - Configuration 5:
+/// - The same as configuration 4, but using the following order of
+/// host-reservation-identifiers: duid, circuit-id, hw-address,
+/// client-id.
+///
+/// - Configuration 6:
+/// - This configuration provides reservations for next-server,
+/// server-hostname and boot-file-name value.
+/// - 1 subnet: 10.0.0.0/24
+/// - 1 reservation for this subnet:
+/// - Client's HW address: aa:bb:cc:dd:ee:ff
+/// - next-server = 10.0.0.7
+/// - server name = "some-name.example.org"
+/// - boot-file-name = "bootfile.efi"
+///
+/// - Configuration 7:
+/// - Used for testing custom value of dhcp-server-identifier option.
+/// - 3 subnets: 10.0.0.0/24, 192.0.2.0/26 and 192.0.2.64/26
+/// - Custom server identifier specified for 2 subnets subnet.
+/// - Custom server identifier specified at global level.
+///
+/// - Configuration 8:
+/// - Simple configuration with a single subnet and single pool
+/// - Using MySQL lease database backend to store leases
+///
+/// - Configuration 9:
+/// - Simple configuration with a single subnet and single pool
+/// - Using PostgreSQL lease database backend to store leases
+///
+/// - Configuration 10:
+/// - Simple configuration with a single subnet
+/// - One in-pool reservation for a circuit-id of 'charter950'
+///
+/// - Configuration 11:
+/// - Simple configuration with a single subnet
+/// - One in-pool reservation for MAC address aa:bb:cc:dd:ee:ff
+/// - The reservations-in-subnet flag is set to true
+///
+/// - Configuration 12:
+/// - Simple configuration with a single subnet as in #12
+/// - The reservations-in-subnet flag is set to false for testing that the
+/// reservations are ignored
+///
+/// - Configuration 13:
+/// - 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 14:
+/// - Use for testing authoritative flag
+/// - 1 subnet: 10.0.0.0/24
+/// - 1 pool: 10.0.0.10-10.0.0.100
+/// - authoritative flag is set to true, thus the server responds
+/// with DHCPNAK to requests from unknown clients.
+///
+/// - Configuration 15:
+/// - Use for testing authoritative flag
+/// - 1 subnet: 10.0.0.0/24
+/// - 1 pool: 10.0.0.10-10.0.0.100
+/// - authoritative flag is set to false, thus the server does not
+/// respond to requests from unknown clients.
+///
+const char* DORA_CONFIGS[] = {
+ // Configuration 0
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"routers\","
+ " \"data\": \"10.0.0.200,10.0.0.201\""
+ " },"
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.202,10.0.0.203\""
+ " },"
+ " {"
+ " \"name\": \"log-servers\","
+ " \"data\": \"10.0.0.200,10.0.0.201\""
+ " },"
+ " {"
+ " \"name\": \"cookie-servers\","
+ " \"data\": \"10.0.0.202,10.0.0.203\""
+ " } ]"
+ " } ]"
+ "}",
+
+ // Configuration 1
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"option-data\": [ {"
+ " \"name\": \"routers\","
+ " \"data\": \"192.0.2.200,192.0.2.201\""
+ " },"
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.202,192.0.2.203\""
+ " },"
+ " {"
+ " \"name\": \"log-servers\","
+ " \"data\": \"10.0.0.200,10.0.0.201\""
+ " },"
+ " {"
+ " \"name\": \"cookie-servers\","
+ " \"data\": \"10.0.0.202,10.0.0.203\""
+ " } ]"
+ " } ]"
+ "}",
+
+ // Configuration 2
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"reservations\": [ "
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"10.0.0.7\""
+ " },"
+ " {"
+ " \"duid\": \"01:02:03:04:05\","
+ " \"ip-address\": \"10.0.0.8\""
+ " },"
+ " {"
+ " \"circuit-id\": \"'charter950'\","
+ " \"ip-address\": \"10.0.0.9\""
+ " },"
+ " {"
+ " \"client-id\": \"01:11:22:33:44:55:66\","
+ " \"ip-address\": \"10.0.0.1\""
+ " }"
+ " ]"
+ "} ]"
+ "}",
+
+ // Configuration 3
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"match-client-id\": false,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"routers\","
+ " \"data\": \"10.0.0.200,10.0.0.201\""
+ " } ]"
+ " } ]"
+ "}",
+
+ // Configuration 4
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"host-reservation-identifiers\": [ \"circuit-id\", \"hw-address\","
+ " \"duid\", \"client-id\" ],"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"reservations\": [ "
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"10.0.0.7\""
+ " },"
+ " {"
+ " \"duid\": \"01:02:03:04:05\","
+ " \"ip-address\": \"10.0.0.8\""
+ " },"
+ " {"
+ " \"circuit-id\": \"'charter950'\","
+ " \"ip-address\": \"10.0.0.9\""
+ " },"
+ " {"
+ " \"client-id\": \"01:11:22:33:44:55:66\","
+ " \"ip-address\": \"10.0.0.1\""
+ " }"
+ " ]"
+ "} ]"
+ "}",
+
+ // Configuration 5
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"host-reservation-identifiers\": [ \"duid\", \"client-id\","
+ " \"circuit-id\", \"hw-address\" ],"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"reservations\": [ "
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"10.0.0.7\""
+ " },"
+ " {"
+ " \"duid\": \"01:02:03:04:05\","
+ " \"ip-address\": \"10.0.0.8\""
+ " },"
+ " {"
+ " \"circuit-id\": \"'charter950'\","
+ " \"ip-address\": \"10.0.0.9\""
+ " },"
+ " {"
+ " \"client-id\": \"01:11:22:33:44:55:66\","
+ " \"ip-address\": \"10.0.0.1\""
+ " }"
+ " ]"
+ "} ]"
+ "}",
+
+ // Configuration 6
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"next-server\": \"10.0.0.1\","
+ "\"server-hostname\": \"nohost\","
+ "\"boot-file-name\": \"nofile\","
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"reservations\": [ "
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"next-server\": \"10.0.0.7\","
+ " \"server-hostname\": \"some-name.example.org\","
+ " \"boot-file-name\": \"bootfile.efi\""
+ " }"
+ " ]"
+ "} ]"
+ "}",
+
+ // Configuration 7
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"option-data\": ["
+ " {"
+ " \"name\": \"dhcp-server-identifier\","
+ " \"data\": \"3.4.5.6\""
+ " }"
+ "],"
+ "\"subnet4\": ["
+ " {"
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"interface\": \"eth0\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dhcp-server-identifier\","
+ " \"data\": \"1.2.3.4\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"192.0.2.0/26\", "
+ " \"pools\": [ { \"pool\": \"192.0.2.10-192.0.2.63\" } ],"
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dhcp-server-identifier\","
+ " \"data\": \"2.3.4.5\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"192.0.2.64/26\", "
+ " \"pools\": [ { \"pool\": \"192.0.2.65-192.0.2.100\" } ],"
+ " \"relay\": {"
+ " \"ip-address\": \"10.2.3.4\""
+ " }"
+ " }"
+ "]"
+ "}",
+
+ // Configuration 8
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"lease-database\": {"
+ " \"type\": \"mysql\","
+ " \"name\": \"keatest\","
+ " \"user\": \"keatest\","
+ " \"password\": \"keatest\""
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ]"
+ " } ]"
+ "}",
+
+ // Configuration 9
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"lease-database\": {"
+ " \"type\": \"postgresql\","
+ " \"name\": \"keatest\","
+ " \"user\": \"keatest\","
+ " \"password\": \"keatest\""
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ]"
+ " } ]"
+ "}",
+
+ // Configuration 10
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"host-reservation-identifiers\": [ \"circuit-id\" ],"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"pools\": [ { \"pool\": \"10.0.0.5-10.0.0.100\" } ],"
+ " \"reservations\": [ "
+ " {"
+ " \"circuit-id\": \"'charter950'\","
+ " \"ip-address\": \"10.0.0.9\""
+ " }"
+ " ]"
+ "} ]"
+ "}",
+
+ // Configuration 11
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": false,"
+ " \"reservations\": [ "
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"10.0.0.65\""
+ " }"
+ " ]"
+ "} ]"
+ "}",
+
+ // Configuration 12
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": false,"
+ " \"reservations\": [ "
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"10.0.0.65\""
+ " }"
+ " ]"
+ "} ]"
+ "}",
+
+ // Configuration 13
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": true,"
+ " \"reservations\": [ "
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"10.0.0.200\""
+ " },"
+ " {"
+ " \"hw-address\": \"11:22:33:44:55:66\","
+ " \"ip-address\": \"10.0.0.65\""
+ " }"
+ " ]"
+ "} ]"
+ "}",
+
+ // Configuration 14
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"authoritative\": true,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"routers\","
+ " \"data\": \"10.0.0.200,10.0.0.201\""
+ " } ]"
+ " } ]"
+ "}",
+
+ // Configuration 15
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"authoritative\": false,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"routers\","
+ " \"data\": \"10.0.0.200,10.0.0.201\""
+ " } ]"
+ " } ]"
+ "}",
+
+ // Configuration 16
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": ["
+ " {"
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"store-extended-info\": true,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"interface\": \"eth0\""
+ " },"
+ " {"
+ " \"subnet\": \"192.0.2.0/26\", "
+ " \"store-extended-info\": false,"
+ " \"pools\": [ { \"pool\": \"192.0.2.10-192.0.2.63\" } ],"
+ " \"interface\": \"eth1\""
+ " }"
+ "]"
+ "}"
+};
+
+/// @brief Test fixture class for testing 4-way (DORA) exchanges.
+///
+/// @todo Currently there is a limit number of test cases covered here.
+/// In the future it is planned that the tests from the
+/// dhcp4_srv_unittest.cc will be migrated here and will use the
+/// @c Dhcp4Client class.
+class DORATest : public Dhcpv4SrvTest {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets up fake interfaces.
+ DORATest()
+ : Dhcpv4SrvTest(),
+ iface_mgr_test_config_(true) {
+ // Let's wipe all existing statistics.
+ isc::stats::StatsMgr::instance().removeAll();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Cleans up statistics after the test.
+ ~DORATest() {
+ // Let's wipe all existing statistics.
+ isc::stats::StatsMgr::instance().removeAll();
+ }
+
+ /// @brief Test that server returns the same lease for the client which is
+ /// sometimes using client identifier, sometimes not.
+ ///
+ /// This test checks the server's behavior in the following scenario:
+ /// - Client identifies itself to the server using HW address, and may use
+ /// client identifier.
+ /// - Client performs the 4-way exchange and obtains a lease from the server.
+ /// - If the client identifier was in use when the client has acquired the lease,
+ /// the client uses null client identifier in the next exchange with the server.
+ /// - If the client identifier was not in use when the client has acquired the
+ /// lease, the client uses client identifier in the next exchange with the
+ /// server.
+ /// - When the client contacts the server for the second time using the
+ /// DHCPDISCOVER the server determines (using HW address) that the client
+ /// already has a lease and returns this lease to the client.
+ /// - The client renews the existing lease.
+ ///
+ /// @param clientid_a Client identifier when the client initially allocates
+ /// the lease. An empty value means "no client identifier".
+ /// @param clientid_b Client identifier when the client sends the DHCPDISCOVER
+ /// and then DHCPREQUEST to renew lease.
+ void oneAllocationOverlapTest(const std::string& clientid_a,
+ const std::string& clientid_b);
+
+ /// @brief Test that the client using the same hardware address but
+ /// multiple client identifiers will obtain multiple leases.
+ ///
+ /// This reflects the scenario of the OS installation over the network
+ /// when BIOS, installer and the host request DHCPv4 lease assignment
+ /// using the same MAC address/interface but generating different
+ /// client identifiers.
+ ///
+ /// @param config_index Index of the configuration within the
+ /// @c DORA_CONFIGS array.
+ void testMultiStageBoot(const unsigned int config_index);
+
+ /// @brief This test verifies that the client in the SELECTING state can get
+ /// an address when it doesn't request any specific address in the
+ /// DHCPDISCOVER message.
+ void selectingDoNotRequestAddress();
+
+ /// @brief This test verifies that multiple clients may use the DHCPv4
+ /// server and obtain unique leases.
+ void selectingMultipleClients();
+
+ /// @brief This test verifies that the client in a SELECTING state can
+ /// request a specific address and that this address will be assigned when
+ /// available. It also tests that if the client requests an address which is
+ /// in use the client will get a different address.
+ void selectingRequestAddress();
+
+ /// @brief This test verifies that the client will get the address that it
+ /// has been allocated when the client requests a different address.
+ void selectingRequestNonMatchingAddress();
+
+ /// @brief Test that the client in the INIT-REBOOT state can request the IP
+ /// address it has and the address is returned. Also, check that if the
+ /// client requests invalid address the server sends a DHCPNAK.
+ void initRebootRequest();
+
+ /// @brief Test that the client in the INIT-REBOOT state can request the IP
+ /// address it has and the address is returned. Also, check that if the
+ /// client is unknown the server sends a DHCPNAK.
+ void authoritative();
+
+ /// @brief Test that the client in the INIT-REBOOT state can request the IP
+ /// address it has and the address is returned. Also, check that if the
+ /// client is unknown the request is dropped.
+ void notAuthoritative();
+
+ /// @brief Check that the ciaddr returned by the server is correct for
+ /// DHCPOFFER and DHCPNAK according to RFC2131, section 4.3.1.
+ void ciaddr();
+
+ /// @brief This test checks the server behavior in the following situation:
+ /// - Client A identifies itself to the server using client identifier
+ /// and the hardware address and requests allocation of the new lease.
+ /// - Server allocates the lease to the client.
+ /// - Client B has the same hardware address but is using a different
+ /// client identifier then Client A.
+ /// - Client B sends DHCPDISCOVER.
+ /// - Server should determine that the client B is not client A, because
+ /// it is using a different client identifier, even though they use the
+ /// same HW address. As a consequence, the server should offer a
+ /// different address to the client B.
+ /// - The client B performs the 4-way exchange again and the server
+ /// allocates a new address to the client, which should be different
+ /// than the address used by the client A.
+ /// - Client B is in the renewing state and it successfully renews its
+ /// address.
+ /// - Client A also renews its address successfully.
+ void twoAllocationsOverlap();
+
+ /// @brief This is a simple test for the host reservation. It creates a
+ /// reservation for an address for a single client, identified by the HW
+ /// address. The test verifies that the client using this HW address will
+ /// obtain a lease for the reserved address. It also checks that the client
+ /// using a different HW address will obtain an address from the dynamic
+ /// pool.
+ void reservation();
+
+ /// @brief This test checks that it is possible to make a reservation by
+ /// DUID carried in the Client Identifier option.
+ void reservationByDUID();
+
+ /// @brief This test checks that it is possible to make a reservation by
+ /// circuit-id inserted by the relay agent.
+ void reservationByCircuitId();
+
+ /// @brief This test checks that it is possible to make a reservation by
+ /// client-id.
+ void reservationByClientId();
+
+ /// @brief This test verifies that order in which host identifiers are used
+ /// to retrieve host reservations can be controlled.
+ void hostIdentifiersOrder();
+
+ /// @brief This test checks that setting the match-client-id value to false
+ /// causes the server to ignore changing client identifier when the client
+ /// is using consistent HW address.
+ void ignoreChangingClientId();
+
+ /// @brief This test checks that the match-client-id parameter doesn't have
+ /// effect on the lease lookup using the HW address.
+ void changingHWAddress();
+
+ /// @brief This test verifies that the server assigns reserved values for
+ /// the siaddr, sname and file fields carried within DHCPv4 message.
+ void messageFieldsReservations();
+
+ /// @brief This test checks the following scenario:
+ /// 1. Client A performs 4-way exchange and obtains a lease from the dynamic
+ /// pool.
+ /// 2. Reservation is created for the client A using an address out of the
+ /// dynamic pool.
+ /// 3. Client A renews the lease.
+ /// 4. Server responds with DHCPNAK to indicate that the client should stop
+ /// using an address for which it has a lease. Server doesn't want to
+ /// renew an address for which the client doesn't have a reservation,
+ /// while it has a reservation for a different address.
+ /// 5. Client A receives a DHCPNAK and returns to the DHCP server discovery.
+ /// 6. Client A performs a 4-way exchange with a server and the server
+ /// allocates a reserved address to the Client A.
+ /// 7. Client A renews the allocated address and the server returns a
+ /// DHCPACK.
+ /// 8. Reservation for the Client A is removed.
+ /// 9. Client A renews the (previously reserved) lease and the server
+ /// returns DHCPNAK because the address in use is neither reserved nor
+ /// belongs to the dynamic pool.
+ /// 10. Client A returns to the DHCP server discovery.
+ /// 11. Client A uses 4-way exchange to obtain a lease from the dynamic
+ /// pool.
+ /// 12. The new address that the Client A is using is reserved for Client B.
+ /// Client A still holds this address.
+ /// 13. Client B uses 4-way exchange to obtain a new lease.
+ /// 14. The server determines that the Client B has a reservation for the
+ /// address which is in use by Client A and offers an address different
+ /// than reserved.
+ /// 15. Client B requests the allocation of the offered address and the
+ /// server allocates this address.
+ /// 16. Client A renews the lease.
+ /// 17. The server determines that the address that Client A is using is
+ /// reserved for Client B. The server returns DHCPNAK to the Client A.
+ /// 18. Client B uses 4-way exchange to obtain the reserved lease but the
+ /// lease for the Client A hasn't been removed yet. Client B is assigned
+ /// the same address it has been using.
+ /// 19. Client A uses 4-way exchange to allocate a new lease.
+ /// 20. The server allocates a new lease from the dynamic pool but it avoids
+ /// allocating the address reserved for the Client B.
+ /// 21. Client B uses 4-way exchange to obtain a new lease.
+ /// 22. The server finally allocates a reserved address to the Client B.
+ void reservationsWithConflicts();
+
+ /// @brief This test verifies that the allocation engine ignores
+ /// reservations when reservations flags are set to "disabled".
+ void reservationModeDisabled();
+
+ /// @brief This test verifies that allocation engine assigns a reserved
+ /// address to the client which doesn't own this reservation. We want to
+ /// avoid such cases in the real deployments, but this is just a test that
+ /// the allocation engine skips checking if the reservation exists when it
+ /// allocates an address. In the real deployment the reservation simply
+ /// wouldn't exist.
+ void reservationIgnoredInDisabledMode();
+
+ /// @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 after a client completes its DORA
+ /// exchange, appropriate statistics are updated.
+ void statisticsDORA();
+
+ /// @brief This test verifies that after a client completes an exchange that
+ /// result in NAK, appropriate statistics are updated.
+ void statisticsNAK();
+
+ /// @brief This test verifies that custom server identifier can be specified
+ /// for a subnet.
+ void customServerIdentifier();
+
+ /// @brief This test verifies that reserved lease is not assigned to a
+ /// client which identifier doesn't match the identifier in the reservation.
+ void changingCircuitId();
+
+ /// @brief Verifies that extended info is stored on the lease when
+ /// store-extended-info is enabled.
+ void storeExtendedInfoEnabled();
+
+ /// @brief Verifies that extended info is not stored on the lease when
+ /// store-extended-info is disabled.
+ void storeExtendedInfoDisabled();
+
+ /// @brief Interface Manager's fake configuration control.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+};
+
+void
+DORATest::selectingDoNotRequestAddress() {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[0], *client.getServer());
+
+ // Perform 4-way exchange with the server but to not request any
+ // specific address in the DHCPDISCOVER message.
+ ASSERT_NO_THROW(client.doDORA());
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Response must not be relayed.
+ EXPECT_FALSE(resp->isRelayed());
+ // Make sure that the server id is present.
+ EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+ // Make sure that the client has got the lease with the requested address.
+ ASSERT_NE(client.config_.lease_.addr_.toText(), "0.0.0.0");
+}
+
+TEST_F(DORATest, selectingDoNotRequestAddress) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ selectingDoNotRequestAddress();
+}
+
+TEST_F(DORATest, selectingDoNotRequestAddressMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ selectingDoNotRequestAddress();
+}
+
+void
+DORATest::selectingMultipleClients() {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[0], *client.getServer());
+
+ // Get the first lease.
+ ASSERT_NO_THROW(client.doDORA());
+
+ // Make sure that the server responded.
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Store the lease.
+ Lease4 lease1 = client.config_.lease_;
+
+ // Get the lease for a different client.
+ client.modifyHWAddr();
+ ASSERT_NO_THROW(client.doDORA());
+ // Make sure that the server responded.
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Store the lease.
+ Lease4 lease2 = client.config_.lease_;
+
+ // Get the lease for a different client.
+ client.modifyHWAddr();
+ ASSERT_NO_THROW(client.doDORA());
+ // Make sure that the server responded.
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Store the lease.
+ Lease4 lease3 = client.config_.lease_;
+
+ // Make sure that unique addresses have been assigned.
+ EXPECT_NE(lease1.addr_, lease2.addr_);
+ EXPECT_NE(lease2.addr_, lease3.addr_);
+ EXPECT_NE(lease1.addr_, lease3.addr_);
+}
+
+TEST_F(DORATest, selectingMultipleClients) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ selectingMultipleClients();
+}
+
+TEST_F(DORATest, selectingMultipleClientsMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ selectingMultipleClients();
+}
+
+void
+DORATest::selectingRequestAddress() {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[0], *client.getServer());
+
+ // Perform 4-way exchange with the server.
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("10.0.0.50"))));
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Response must not be relayed.
+ EXPECT_FALSE(resp->isRelayed());
+ // Make sure that the server id is present.
+ EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+ // Make sure that the client has got the lease with the requested address.
+ ASSERT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
+
+ // Simulate different client requesting the same address.
+ client.modifyHWAddr();
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("10.0.0.50"))));
+ resp = client.getContext().response_;
+ // Make sure that the server responded.
+ ASSERT_TRUE(resp);
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Response must not be relayed.
+ EXPECT_FALSE(resp->isRelayed());
+ // Make sure that the server id is present.
+ EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+ // Make sure that the client has got some address.
+ EXPECT_NE(client.config_.lease_.addr_.toText(), "0.0.0.0");
+ // Make sure that the client has got a different address than requested
+ // as the requested one is already in use.
+ EXPECT_NE(client.config_.lease_.addr_.toText(), "10.0.0.50");
+}
+
+TEST_F(DORATest, selectingRequestAddress) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ selectingRequestAddress();
+}
+
+TEST_F(DORATest, selectingRequestAddressMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ selectingRequestAddress();
+}
+
+void
+DORATest::selectingRequestNonMatchingAddress() {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[0], *client.getServer());
+
+ // Perform 4-way exchange with the server.
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("10.0.0.50"))));
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Response must not be relayed.
+ EXPECT_FALSE(resp->isRelayed());
+ // Make sure that the server id is present.
+ EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+ // Make sure that the client has got the lease with the requested address.
+ ASSERT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
+
+ // Let's request a different address. The server should respond with
+ // the one that the client already has allocated.
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("10.0.0.80"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Response must not be relayed.
+ EXPECT_FALSE(resp->isRelayed());
+ // Make sure that the server id is present.
+ EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+ // Make sure that the client has got the lease with the address that
+ // the client has recorded in the lease database.
+ EXPECT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
+}
+
+TEST_F(DORATest, selectingRequestNonMatchingAddress) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ selectingRequestNonMatchingAddress();
+}
+
+TEST_F(DORATest, selectingRequestNonMatchingAddressMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ selectingRequestNonMatchingAddress();
+}
+
+void
+DORATest::initRebootRequest() {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[0], *client.getServer());
+ client.includeClientId("11:22");
+ // Obtain a lease from the server using the 4-way exchange.
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("10.0.0.50"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Response must not be relayed.
+ EXPECT_FALSE(resp->isRelayed());
+ // Make sure that the server id is present.
+ EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+ // Make sure that the client has got the lease with the requested address.
+ ASSERT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
+
+ // Client has a lease in the database. Let's transition the client
+ // to the INIT_REBOOT state so as the client can request the cached
+ // lease using the DHCPREQUEST message.
+ client.setState(Dhcp4Client::INIT_REBOOT);
+ ASSERT_NO_THROW(client.doRequest());
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Response must not be relayed.
+ EXPECT_FALSE(resp->isRelayed());
+ // Make sure that the server id is present.
+ EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+ // Make sure that the client has got the lease with the requested address.
+ ASSERT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
+
+ // Try to request a different address than the client has. The server
+ // should respond with DHCPNAK.
+ client.config_.lease_.addr_ = IOAddress("10.0.0.30");
+ ASSERT_NO_THROW(client.doRequest());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ EXPECT_EQ(DHCPNAK, static_cast<int>(resp->getType()));
+
+ // Change client identifier. The server should treat the request
+ // as a request from unknown client and ignore it.
+ client.includeClientId("12:34");
+ ASSERT_NO_THROW(client.doRequest());
+ ASSERT_FALSE(client.getContext().response_);
+
+ // Now let's fix the IP address. The client identifier is still
+ // invalid so the message should be dropped.
+ client.config_.lease_.addr_ = IOAddress("10.0.0.50");
+ ASSERT_NO_THROW(client.doRequest());
+ ASSERT_FALSE(client.getContext().response_);
+
+ // Restore original client identifier.
+ client.includeClientId("11:22");
+
+ // Try to request from a different HW address. This should be successful
+ // because the client identifier matches.
+ client.modifyHWAddr();
+ ASSERT_NO_THROW(client.doRequest());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Make sure that the client has got the lease with the requested address.
+ ASSERT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
+}
+
+TEST_F(DORATest, initRebootRequest) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ initRebootRequest();
+}
+
+TEST_F(DORATest, initRebootRequestMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ initRebootRequest();
+}
+
+void
+DORATest::authoritative() {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[14], *client.getServer());
+ client.includeClientId("11:22");
+
+ // Try to renew an address that is outside the pool.
+ client.setState(Dhcp4Client::RENEWING);
+ client.ciaddr_ = IOAddress("10.0.0.9");
+ ASSERT_NO_THROW_LOG(client.doRequest());
+ // Even though we're authoritative server should not respond
+ // since it does not know this address.
+ ASSERT_FALSE(client.getContext().response_);
+
+ // Obtain a lease from the server using the 4-way exchange.
+ client.ciaddr_ = IOAddress("0.0.0.0");
+ client.setState(Dhcp4Client::SELECTING);
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("10.0.0.50"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Response must not be relayed.
+ EXPECT_FALSE(resp->isRelayed());
+ // Make sure that the server id is present.
+ EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+ // Make sure that the client has got the lease with the requested address.
+ ASSERT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
+
+ // Client has a lease in the database. Let's transition the client
+ // to the INIT_REBOOT state so as the client can request the cached
+ // lease using the DHCPREQUEST message.
+ client.setState(Dhcp4Client::INIT_REBOOT);
+ ASSERT_NO_THROW(client.doRequest());
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Response must not be relayed.
+ EXPECT_FALSE(resp->isRelayed());
+ // Make sure that the server id is present.
+ EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+ // Make sure that the client has got the lease with the requested address.
+ ASSERT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
+
+ // Try to request a different address than the client has. The server
+ // should respond with DHCPNAK.
+ client.config_.lease_.addr_ = IOAddress("10.0.0.30");
+ ASSERT_NO_THROW(client.doRequest());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ EXPECT_EQ(DHCPNAK, static_cast<int>(resp->getType()));
+
+ // Try to request another different address from an unknown subnet.
+ // The server should respond with DHCPNAK.
+ client.config_.lease_.addr_ = IOAddress("10.1.0.30");
+ ASSERT_NO_THROW(client.doRequest());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ EXPECT_EQ(DHCPNAK, static_cast<int>(resp->getType()));
+
+ // Change client identifier. The server should treat the request
+ // as a request from unknown client and respond with DHCPNAK.
+ client.includeClientId("12:34");
+ client.config_.lease_.addr_ = IOAddress("10.1.0.30");
+ ASSERT_NO_THROW(client.doRequest());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ EXPECT_EQ(DHCPNAK, static_cast<int>(resp->getType()));
+
+ // Now let's fix the IP address. The client identifier is still
+ // invalid so the server still responds with DHCPNAK.
+
+ ASSERT_NO_THROW(client.doRequest());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ EXPECT_EQ(DHCPNAK, static_cast<int>(resp->getType()));
+
+ // Restore original client identifier.
+ client.includeClientId("11:22");
+ client.config_.lease_.addr_ = IOAddress("10.0.0.50");
+
+ // Try to request from a different HW address. This should be successful
+ // because the client identifier matches.
+ client.modifyHWAddr();
+ ASSERT_NO_THROW(client.doRequest());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Make sure that the client has got the lease with the requested address.
+ ASSERT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
+}
+
+TEST_F(DORATest, authoritative) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ authoritative();
+}
+
+TEST_F(DORATest, authoritativeMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ authoritative();
+}
+
+void
+DORATest::notAuthoritative() {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[15], *client.getServer());
+ client.includeClientId("11:22");
+
+ // Try to renew an address that is outside the pool.
+ client.setState(Dhcp4Client::RENEWING);
+ client.ciaddr_ = IOAddress("10.0.0.9");
+ ASSERT_NO_THROW_LOG(client.doRequest());
+ // We are not authoritative sure that the server does
+ // not respond at all.
+ ASSERT_FALSE(client.getContext().response_);
+
+ // Obtain a lease from the server using the 4-way exchange.
+ client.ciaddr_ = IOAddress("0.0.0.0");
+ client.setState(Dhcp4Client::SELECTING);
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("10.0.0.50"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Response must not be relayed.
+ EXPECT_FALSE(resp->isRelayed());
+ // Make sure that the server id is present.
+ EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+ // Make sure that the client has got the lease with the requested address.
+ ASSERT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
+
+ // Client has a lease in the database. Let's transition the client
+ // to the INIT_REBOOT state so as the client can request the cached
+ // lease using the DHCPREQUEST message.
+ client.setState(Dhcp4Client::INIT_REBOOT);
+ ASSERT_NO_THROW(client.doRequest());
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Response must not be relayed.
+ EXPECT_FALSE(resp->isRelayed());
+ // Make sure that the server id is present.
+ EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+ // Make sure that the client has got the lease with the requested address.
+ ASSERT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
+
+ // Try to request a different address than the client has. The server
+ // should respond with DHCPNAK.
+ client.config_.lease_.addr_ = IOAddress("10.0.0.30");
+ ASSERT_NO_THROW(client.doRequest());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ EXPECT_EQ(DHCPNAK, static_cast<int>(resp->getType()));
+
+ // Try to request another different address from an unknown subnet.
+ // The server should respond with DHCPNAK.
+ client.config_.lease_.addr_ = IOAddress("10.1.0.30");
+ ASSERT_NO_THROW(client.doRequest());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ ASSERT_EQ(DHCPNAK, static_cast<int>(resp->getType()));
+
+ // Change client identifier. The server should treat the request
+ // as a request from unknown client and not respond (no DHCPNAK).
+ // Changed behavior vs authoritative!
+ client.includeClientId("12:34");
+ client.config_.lease_.addr_ = IOAddress("10.1.0.30");
+ ASSERT_NO_THROW(client.doRequest());
+ // Make sure that the server did not respond.
+ EXPECT_FALSE(client.getContext().response_);
+
+ // Now let's fix the IP address. The client identifier is still
+ // invalid so the message should be dropped (no DHCPNAK).
+ // Changed behavior vs authoritative!
+ client.config_.lease_.addr_ = IOAddress("10.0.0.50");
+ ASSERT_NO_THROW(client.doRequest());
+ // Make sure that the server did not respond.
+ EXPECT_FALSE(client.getContext().response_);
+
+ // Restore original client identifier.
+ client.includeClientId("11:22");
+ client.config_.lease_.addr_ = IOAddress("10.0.0.50");
+
+ // Try to request from a different HW address. This should be successful
+ // because the client identifier matches.
+ client.modifyHWAddr();
+ ASSERT_NO_THROW(client.doRequest());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Make sure that the client has got the lease with the requested address.
+ ASSERT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
+}
+
+TEST_F(DORATest, notAuthoritative) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ notAuthoritative();
+}
+
+TEST_F(DORATest, notAuthoritativeMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ notAuthoritative();
+}
+
+void
+DORATest::ciaddr() {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[0], *client.getServer());
+ // Force ciaddr of Discover message to be non-zero.
+ client.ciaddr_ = IOAddress("10.0.0.50");
+ // Obtain a lease from the server using the 4-way exchange.
+ ASSERT_NO_THROW(client.doDiscover(boost::shared_ptr<
+ IOAddress>(new IOAddress("10.0.0.50"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPOFFER.
+ ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+ // Make sure ciaddr is not set for DHCPOFFER.
+ EXPECT_EQ("0.0.0.0", resp->getCiaddr().toText());
+
+ // Obtain a lease from the server using the 4-way exchange.
+ ASSERT_NO_THROW(client.doRequest());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Let's transition the client to Renewing state.
+ client.setState(Dhcp4Client::RENEWING);
+
+ // Set the unicast destination address to indicate that it is a renewal.
+ client.setDestAddress(IOAddress("10.0.0.1"));
+ ASSERT_NO_THROW(client.doRequest());
+ // The client is sending invalid ciaddr so the server should send a NAK.
+ resp = client.getContext().response_;
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // For DHCPACK the ciaddr may be 0 or may be set to the ciaddr value
+ // from the client's message. Kea sets it to the latter.
+ EXPECT_EQ("10.0.0.50", resp->getCiaddr().toText());
+
+ // Replace the address held by the client. The client will request
+ // the assignment of this address but the server has a different
+ // address for this client.
+ client.ciaddr_ = IOAddress("192.168.0.30");
+ ASSERT_NO_THROW(client.doRequest());
+ // The client is sending invalid ciaddr so the server should send a NAK.
+ resp = client.getContext().response_;
+ ASSERT_EQ(DHCPNAK, static_cast<int>(resp->getType()));
+ // For DHCPNAK the ciaddr is always 0 (should not be copied) from the
+ // client's message.
+ EXPECT_EQ("0.0.0.0", resp->getCiaddr().toText());
+}
+
+TEST_F(DORATest, ciaddr) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ ciaddr();
+}
+
+TEST_F(DORATest, ciaddrMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ ciaddr();
+}
+
+void
+DORATest::oneAllocationOverlapTest(const std::string& clientid_a,
+ const std::string& clientid_b) {
+ // Allocate a lease by client using the 4-way exchange.
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.includeClientId(clientid_a);
+ client.setHWAddress("01:02:03:04:05:06");
+ configure(DORA_CONFIGS[0], *client.getServer());
+ ASSERT_NO_THROW(client.doDORA());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ Lease4Ptr lease_a = LeaseMgrFactory::instance().getLease4(client.config_.lease_.addr_);
+ ASSERT_TRUE(lease_a);
+ // Remember the allocated address.
+ IOAddress yiaddr = lease_a->addr_;
+
+ // Change client identifier. If parameters clientid_a and clientid_b
+ // are specified correctly, this removes the client identifier from
+ // client's requests if the lease has been acquired with the client
+ // identifier, or adds the client identifier otherwise.
+ client.includeClientId(clientid_b);
+
+ // Check if the server will offer the same address.
+ ASSERT_NO_THROW(client.doDiscover());
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ EXPECT_EQ(yiaddr, resp->getYiaddr());
+
+ // Client should also be able to renew its address.
+ client.setState(Dhcp4Client::RENEWING);
+ ASSERT_NO_THROW(client.doRequest());
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ ASSERT_EQ(yiaddr, client.config_.lease_.addr_);
+}
+
+void
+DORATest::twoAllocationsOverlap() {
+ // Allocate a lease by client A using the 4-way exchange.
+ Dhcp4Client client_a(Dhcp4Client::SELECTING);
+ client_a.includeClientId("12:34");
+ client_a.setHWAddress("01:02:03:04:05:06");
+ configure(DORA_CONFIGS[0], *client_a.getServer());
+ ASSERT_NO_THROW(client_a.doDORA());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client_a.getContext().response_);
+ Pkt4Ptr resp_a = client_a.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp_a->getType()));
+
+ // Make sure that the lease has been recorded by the server.
+ Lease4Ptr lease_a = LeaseMgrFactory::instance().getLease4(client_a.config_.lease_.addr_);
+ ASSERT_TRUE(lease_a);
+
+ // Create client B.
+ Dhcp4Client client_b(client_a.getServer(), Dhcp4Client::SELECTING);
+ client_b.setHWAddress("01:02:03:04:05:06");
+ client_b.includeClientId("45:67");
+ // Send DHCPDISCOVER and expect the response.
+ ASSERT_NO_THROW(client_b.doDiscover());
+ Pkt4Ptr resp_b = client_b.getContext().response_;
+ // Make sure that the server has responded with DHCPOFFER.
+ ASSERT_EQ(DHCPOFFER, static_cast<int>(resp_b->getType()));
+ // The offered address should be different than the address which
+ // was obtained by the client A.
+ ASSERT_NE(resp_b->getYiaddr(), client_a.config_.lease_.addr_);
+
+ // Make sure that the client A lease hasn't been modified.
+ lease_a = LeaseMgrFactory::instance().getLease4(client_a.config_.lease_.addr_);
+ ASSERT_TRUE(lease_a);
+
+ // Now that we know that the server will avoid assigning the same
+ // address that the client A has, use the 4-way exchange to actually
+ // allocate some address.
+ ASSERT_NO_THROW(client_b.doDORA());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client_b.getContext().response_);
+ resp_b = client_b.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp_b->getType()));
+ // Again, make sure the assigned addresses are different.
+ ASSERT_NE(client_b.config_.lease_.addr_, client_a.config_.lease_.addr_);
+
+ // Make sure that the client A still has a lease.
+ lease_a = LeaseMgrFactory::instance().getLease4(client_a.config_.lease_.addr_);
+ ASSERT_TRUE(lease_a);
+
+ // Make sure that the client B has a lease.
+ Lease4Ptr lease_b = LeaseMgrFactory::instance().getLease4(client_b.config_.lease_.addr_);
+ ASSERT_TRUE(lease_b);
+
+ // Client B should be able to renew its address.
+ client_b.setState(Dhcp4Client::RENEWING);
+ ASSERT_NO_THROW(client_b.doRequest());
+ ASSERT_TRUE(client_b.getContext().response_);
+ resp_b = client_b.getContext().response_;
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp_b->getType()));
+ ASSERT_NE(client_b.config_.lease_.addr_, client_a.config_.lease_.addr_);
+
+ // Client A should also be able to renew its address.
+ client_a.setState(Dhcp4Client::RENEWING);
+ ASSERT_NO_THROW(client_a.doRequest());
+ ASSERT_TRUE(client_a.getContext().response_);
+ resp_b = client_a.getContext().response_;
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp_b->getType()));
+ ASSERT_NE(client_a.config_.lease_.addr_, client_b.config_.lease_.addr_);
+}
+
+TEST_F(DORATest, twoAllocationsOverlap) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ twoAllocationsOverlap();
+}
+
+TEST_F(DORATest, twoAllocationsOverlapMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ twoAllocationsOverlap();
+}
+
+// This test checks the server behavior in the following situation:
+// - Client A identifies itself to the server using the hardware address
+// and client identifier.
+// - Client A performs the 4-way exchange and obtains a lease from the server.
+// - Client B uses the same HW address as the client A, but it doesn't use
+// the client identifier.
+// - Client B sends the DHCPDISCOVER to the server.
+// The server determines that there is a lease for the client A using the
+// same HW address as the client B. Server discards the client's message and
+// doesn't offer the lease for the client B to prevent allocation of the
+// lease without a unique identifier.
+// - The client sends the DHCPREQUEST and the server sends the DHCPNAK for the
+// same reason.
+// - Client A renews its address successfully.
+TEST_F(DORATest, oneAllocationOverlap1) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ oneAllocationOverlapTest("12:34", "");
+}
+
+// This test checks the server behavior in the following situation:
+// - Client A identifies itself to the server using the hardware address
+// and client identifier.
+// - Client A performs the 4-way exchange and obtains a lease from the server.
+// - Client B uses the same HW address as the client A, but it doesn't use
+// the client identifier.
+// - Client B sends the DHCPDISCOVER to the server.
+// The server determines that there is a lease for the client A using the
+// same HW address as the client B. Server discards the client's message and
+// doesn't offer the lease for the client B to prevent allocation of the
+// lease without a unique identifier.
+// - The client sends the DHCPREQUEST and the server sends the DHCPNAK for the
+// same reason.
+// - Client A renews its address successfully.
+TEST_F(DORATest, oneAllocationOverlap1MultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ oneAllocationOverlapTest("12:34", "");
+}
+
+// This test is similar to oneAllocationOverlap2 but this time the client A
+// uses no client identifier, and the client B uses the HW address and the
+// client identifier. The server behaves as previously.
+TEST_F(DORATest, oneAllocationOverlap2) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ oneAllocationOverlapTest("", "12:34");
+}
+
+// This test is similar to oneAllocationOverlap2 but this time the client A
+// uses no client identifier, and the client B uses the HW address and the
+// client identifier. The server behaves as previously.
+TEST_F(DORATest, oneAllocationOverlap2MultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ oneAllocationOverlapTest("", "12:34");
+}
+
+void
+DORATest::reservation() {
+ // Client A is a one which will have a reservation.
+ Dhcp4Client clientA(Dhcp4Client::SELECTING);
+ // Set explicit HW address so as it matches the reservation in the
+ // configuration used below.
+ clientA.setHWAddress("aa:bb:cc:dd:ee:ff");
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[2], *clientA.getServer());
+ // Client A performs 4-way exchange and should obtain a reserved
+ // address.
+ ASSERT_NO_THROW(clientA.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("0.0.0.0"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(clientA.getContext().response_);
+ Pkt4Ptr resp = clientA.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Make sure that the client has got the lease for the reserved address.
+ ASSERT_EQ("10.0.0.7", clientA.config_.lease_.addr_.toText());
+
+ // Client B uses the same server as Client A.
+ Dhcp4Client clientB(clientA.getServer(), Dhcp4Client::SELECTING);
+ // Client B has no reservation so it should get the lease from
+ // the dynamic pool.
+ ASSERT_NO_THROW(clientB.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("0.0.0.0"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(clientB.getContext().response_);
+ resp = clientB.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Obtain the subnet to which the returned address belongs.
+ Subnet4Ptr subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->
+ selectSubnet(clientB.config_.lease_.addr_);
+ ASSERT_TRUE(subnet);
+ // Make sure that the address has been allocated from the dynamic pool.
+ ASSERT_TRUE(subnet->inPool(Lease::TYPE_V4, clientB.config_.lease_.addr_));
+}
+
+TEST_F(DORATest, reservation) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ reservation();
+}
+
+TEST_F(DORATest, reservationMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ reservation();
+}
+
+void
+DORATest:: reservationByDUID() {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Use relay agent.
+ client.useRelay(true, IOAddress("10.0.0.1"), IOAddress("10.0.0.2"));
+ // Modify HW address so as the server doesn't assign reserved
+ // address by HW address.
+ client.modifyHWAddr();
+ // Specify DUID for which address 10.0.0.8 is reserved.
+ // The value specified as client id includes:
+ // - FF is a client identifier type for DUID,
+ // - 45454545 - represents 4 bytes for IAID
+ // - 01:02:03:04:05 - is an actual DUID for which there is a
+ // reservation.
+ client.includeClientId("FF:45:45:45:45:01:02:03:04:05");
+
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[2], *client.getServer());
+ // Client A performs 4-way exchange and should obtain a reserved
+ // address.
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("0.0.0.0"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Make sure that the client has got the lease for the reserved address.
+ ASSERT_EQ("10.0.0.8", client.config_.lease_.addr_.toText());
+}
+
+TEST_F(DORATest, reservationByDUID) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ reservationByDUID();
+}
+
+TEST_F(DORATest, reservationByDUIDMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ reservationByDUID();
+}
+
+void
+DORATest::reservationByCircuitId() {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Use relay agent so as the circuit-id can be inserted.
+ client.useRelay(true, IOAddress("10.0.0.1"), IOAddress("10.0.0.2"));
+ // Specify circuit-id.
+ client.setCircuitId("charter950");
+
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[2], *client.getServer());
+ // Client A performs 4-way exchange and should obtain a reserved
+ // address.
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("0.0.0.0"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Make sure that the client has got the lease for the reserved address.
+ ASSERT_EQ("10.0.0.9", client.config_.lease_.addr_.toText());
+}
+
+TEST_F(DORATest, reservationByCircuitId) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ reservationByCircuitId();
+}
+
+TEST_F(DORATest, reservationByCircuitIdMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ reservationByCircuitId();
+}
+
+void
+DORATest::reservationByClientId() {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Use relay agent to make sure that the desired subnet is
+ // selected for our client.
+ client.useRelay(true, IOAddress("10.0.0.20"), IOAddress("10.0.0.21"));
+ // Specify client identifier.
+ client.includeClientId("01:11:22:33:44:55:66");
+
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[2], *client.getServer());
+ // Client A performs 4-way exchange and should obtain a reserved
+ // address.
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("0.0.0.0"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Make sure that the client has got the lease for the reserved address.
+ ASSERT_EQ("10.0.0.1", client.config_.lease_.addr_.toText());
+}
+
+TEST_F(DORATest, reservationByClientId) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ reservationByClientId();
+}
+
+TEST_F(DORATest, reservationByClientIdMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ reservationByClientId();
+}
+
+void
+DORATest::hostIdentifiersOrder() {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+ // Use relay agent so as the circuit-id can be inserted.
+ client.useRelay(true, IOAddress("10.0.0.1"), IOAddress("10.0.0.2"));
+ // Specify DUID for which address 10.0.0.8 is reserved.
+ // The value specified as client id includes:
+ // - FF is a client identifier type for DUID,
+ // - 45454545 - represents 4 bytes for IAID
+ // - 01:02:03:04:05 - is an actual DUID for which there is a
+ // reservation.
+ client.includeClientId("FF:45:45:45:45:01:02:03:04:05");
+ // Specify circuit-id.
+ client.setCircuitId("charter950");
+
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[2], *client.getServer());
+ // Perform 4-way exchange to obtain reserved address.
+ // The client has in fact two reserved addresses, but the one assigned
+ // should be by hw-address.
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("0.0.0.0"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Make sure that the client has got the lease for the reserved address.
+ ASSERT_EQ("10.0.0.7", client.config_.lease_.addr_.toText());
+
+ // Reconfigure the server to change the preference order of the
+ // host identifiers. The 'circuit-id' should now take precedence over
+ // the hw-address, duid and client-id.
+ configure(DORA_CONFIGS[4], *client.getServer());
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("0.0.0.0"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Make sure that the client has got the lease for the reserved address.
+ ASSERT_EQ("10.0.0.9", client.config_.lease_.addr_.toText());
+
+ // Reconfigure the server to change the preference order of the
+ // host identifiers. The 'duid' should now take precedence over
+ // the client-id, hw-address and circuit-id
+ configure(DORA_CONFIGS[5], *client.getServer());
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("0.0.0.0"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Make sure that the client has got the lease for the reserved address.
+ ASSERT_EQ("10.0.0.8", client.config_.lease_.addr_.toText());
+
+ // Replace the client identifier with the one for which address
+ // 10.0.0.1 is reserved. Because the DUID is a special type of
+ // client identifier, this change effectively removes the association
+ // of the client with the DUID for which address 10.0.0.8 is reserved.
+ // The next identifier type to be used by the server (after DUID) is
+ // client-id and thus the server should assign address 10.0.0.1.
+ client.includeClientId("01:11:22:33:44:55:66");
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("0.0.0.0"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Make sure that the client has got the lease for the reserved address.
+ ASSERT_EQ("10.0.0.1", client.config_.lease_.addr_.toText());
+}
+
+TEST_F(DORATest, hostIdentifiersOrder) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ hostIdentifiersOrder();
+}
+
+TEST_F(DORATest, hostIdentifiersOrderMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ hostIdentifiersOrder();
+}
+
+void
+DORATest::ignoreChangingClientId() {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[3], *client.getServer());
+ client.includeClientId("12:12");
+ // Obtain the lease using 4-way exchange.
+ ASSERT_NO_THROW(client.doDORA());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ EXPECT_FALSE(client.config_.lease_.client_id_);
+
+ // Remember address which the client has obtained.
+ IOAddress leased_address = client.config_.lease_.addr_;
+
+ // Modify client id. Because we have set the configuration flag which
+ // forces the server to lookup leases using the HW address, the
+ // client id modification should not matter and the client should
+ // obtain the same lease.
+ client.includeClientId("14:14");
+ // Obtain the lease using 4-way exchange.
+ ASSERT_NO_THROW(client.doDORA());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Make sure that the server assigned the same address, even though the
+ // client id has changed.
+ EXPECT_EQ(leased_address, client.config_.lease_.addr_);
+ // Check that the client id is not present in the lease.
+ EXPECT_FALSE(client.config_.lease_.client_id_);
+}
+
+TEST_F(DORATest, ignoreChangingClientId) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ ignoreChangingClientId();
+}
+
+TEST_F(DORATest, ignoreChangingClientIdMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ ignoreChangingClientId();
+}
+
+void
+DORATest::changingHWAddress() {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[3], *client.getServer());
+ client.includeClientId("12:12");
+ client.setHWAddress("00:01:02:03:04:05");
+ // Obtain the lease using 4-way exchange.
+ ASSERT_NO_THROW(client.doDORA());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Check that the client id is not present in the lease.
+ EXPECT_FALSE(client.config_.lease_.client_id_);
+
+ // Remember address which the client has obtained.
+ IOAddress leased_address = client.config_.lease_.addr_;
+
+ // Modify HW address but leave client id in place. The value of the
+ // match-client-id set to false must not have any effect on the
+ // case when the HW address is changing. In such case the server will
+ // allocate the new address for the client.
+ client.setHWAddress("01:01:01:01:01:01");
+ // Obtain a lease.
+ ASSERT_NO_THROW(client.doDORA());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Client must assign different address because the client id is
+ // ignored and the HW address was changed.
+ EXPECT_NE(client.config_.lease_.addr_, leased_address);
+ // Check that the client id is not present in the lease.
+ EXPECT_FALSE(client.config_.lease_.client_id_);
+}
+
+TEST_F(DORATest, changingHWAddress) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ changingHWAddress();
+}
+
+TEST_F(DORATest, changingHWAddressMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ changingHWAddress();
+}
+
+void
+DORATest::messageFieldsReservations() {
+ // Client has a reservation.
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Set explicit HW address so as it matches the reservation in the
+ // configuration used below.
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[6], *client.getServer());
+ // Client performs 4-way exchange and should obtain a reserved
+ // address and fixed fields.
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("0.0.0.0"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Check that the reserved values have been assigned.
+ EXPECT_EQ("10.0.0.7", client.config_.siaddr_.toText());
+ EXPECT_EQ("some-name.example.org", client.config_.sname_);
+ EXPECT_EQ("bootfile.efi", client.config_.boot_file_name_);
+}
+
+TEST_F(DORATest, messageFieldsReservations) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ messageFieldsReservations();
+}
+
+TEST_F(DORATest, messageFieldsReservationsMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ messageFieldsReservations();
+}
+
+void
+DORATest::reservationsWithConflicts() {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[0], *client.getServer());
+ // Client A performs 4-way exchange and obtains a lease from the
+ // dynamic pool.
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("10.0.0.50"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Make sure that the client has got the lease with the requested address.
+ ASSERT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
+
+ configure(DORA_CONFIGS[0], false);
+ // Reservation is created for the client A using an address out of the
+ // dynamic pool.
+ HostPtr host(new Host(&client.getHWAddress()->hwaddr_[0],
+ client.getHWAddress()->hwaddr_.size(),
+ Host::IDENT_HWADDR, SubnetID(1),
+ SUBNET_ID_UNUSED, IOAddress("10.0.0.9")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ // Let's transition the client to Renewing state.
+ client.setState(Dhcp4Client::RENEWING);
+
+ // Set the unicast destination address to indicate that it is a renewal.
+ client.setDestAddress(IOAddress("10.0.0.1"));
+ ASSERT_NO_THROW(client.doRequest());
+
+ // Client should get the DHCPNAK from the server because the client has
+ // a reservation for a different address that it is trying to renew.
+ resp = client.getContext().response_;
+ ASSERT_EQ(DHCPNAK, static_cast<int>(resp->getType()));
+
+ // A conforming client would go back to the server discovery.
+ client.setState(Dhcp4Client::SELECTING);
+ // Obtain a lease from the server using the 4-way exchange.
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("0.0.0.0"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK with a reserved
+ // address
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ ASSERT_EQ("10.0.0.9", client.config_.lease_.addr_.toText());
+
+ // Client A renews the allocated address.
+ client.setState(Dhcp4Client::RENEWING);
+ // Set the unicast destination address to indicate that it is a renewal.
+ client.setDestAddress(IOAddress("10.0.0.1"));
+ ASSERT_NO_THROW(client.doRequest());
+ // Make sure the server responded and renewed the client's address.
+ resp = client.getContext().response_;
+ ASSERT_EQ("10.0.0.9", client.config_.lease_.addr_.toText());
+
+ // By reconfiguring the server, we remove the existing reservations.
+ configure(DORA_CONFIGS[0]);
+
+ // Try to renew the existing lease again.
+ ASSERT_NO_THROW(client.doRequest());
+
+ // The reservation has been removed. Since address that the client is
+ // using doesn't belong to a dynamic pool and the server is not
+ // authoritative it should not send a DHCPNAK.
+ resp = client.getContext().response_;
+ ASSERT_FALSE(resp);
+
+ // A conforming client would go back to the server discovery.
+ client.setState(Dhcp4Client::SELECTING);
+ // Obtain a lease from the server using the 4-way exchange.
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("0.0.0.0"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Obtain the subnet to which the returned address belongs.
+ Subnet4Ptr subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->
+ selectSubnet(client.config_.lease_.addr_);
+ ASSERT_TRUE(subnet);
+ // Make sure that the address has been allocated from the dynamic pool.
+ ASSERT_TRUE(subnet->inPool(Lease::TYPE_V4, client.config_.lease_.addr_));
+
+ // Remember the address allocated in the dynamic pool.
+ IOAddress in_pool_addr = client.config_.lease_.addr_;
+
+ // Create Client B.
+ Dhcp4Client clientB(client.getServer());
+ clientB.modifyHWAddr();
+
+ // Create reservation for the Client B, for the address that the
+ // Client A is using.
+ configure(DORA_CONFIGS[0], false);
+ host.reset(new Host(&clientB.getHWAddress()->hwaddr_[0],
+ clientB.getHWAddress()->hwaddr_.size(),
+ Host::IDENT_HWADDR, SubnetID(1),
+ SUBNET_ID_UNUSED, in_pool_addr));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ // Client B performs a DHCPDISCOVER.
+ clientB.setState(Dhcp4Client::SELECTING);
+ // The server determines that the address reserved for Client B is
+ // in use by Client A so it offers a different address.
+ ASSERT_NO_THROW(clientB.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("0.0.0.0"))));
+ ASSERT_TRUE(clientB.getContext().response_);
+ ASSERT_EQ(DHCPACK, static_cast<int>(clientB.getContext().response_->getType()));
+ IOAddress client_b_addr = clientB.config_.lease_.addr_;
+ ASSERT_NE(client_b_addr, in_pool_addr);
+ // Ensure stats are being recorded for HR conflicts
+ ObservationPtr subnet_conflicts = StatsMgr::instance().getObservation(
+ "subnet[1].v4-reservation-conflicts");
+ ASSERT_TRUE(subnet_conflicts);
+ ASSERT_EQ(1, subnet_conflicts->getInteger().first);
+ subnet_conflicts = StatsMgr::instance().getObservation("v4-reservation-conflicts");
+ ASSERT_TRUE(subnet_conflicts);
+ ASSERT_EQ(1, subnet_conflicts->getInteger().first);
+
+ // Client A renews the lease.
+ client.setState(Dhcp4Client::RENEWING);
+ // Set the unicast destination address to indicate that it is a renewal.
+ client.setDestAddress(IOAddress(in_pool_addr));
+ ASSERT_NO_THROW(client.doRequest());
+ // Client A should get a DHCPNAK because it is using an address reserved
+ // for Client B.
+ resp = client.getContext().response_;
+ ASSERT_EQ(DHCPNAK, static_cast<int>(resp->getType()));
+
+ // Client B performs 4-way exchange but still gets an address from the
+ // dynamic pool, because Client A hasn't obtained a new lease, so it is
+ // still using an address reserved for Client B.
+ clientB.setState(Dhcp4Client::SELECTING);
+ // Obtain a lease from the server using the 4-way exchange.
+ ASSERT_NO_THROW(clientB.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("0.0.0.0"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(clientB.getContext().response_);
+ ASSERT_EQ(DHCPACK, static_cast<int>(clientB.getContext().response_->getType()));
+ ASSERT_NE(clientB.config_.lease_.addr_, in_pool_addr);
+ ASSERT_EQ(client_b_addr, clientB.config_.lease_.addr_);
+ // Ensure stats are being recorded for HR conflicts
+ subnet_conflicts = StatsMgr::instance().getObservation(
+ "subnet[1].v4-reservation-conflicts");
+ ASSERT_TRUE(subnet_conflicts);
+ ASSERT_EQ(2, subnet_conflicts->getInteger().first);
+ subnet_conflicts = StatsMgr::instance().getObservation("v4-reservation-conflicts");
+ ASSERT_TRUE(subnet_conflicts);
+ ASSERT_EQ(2, subnet_conflicts->getInteger().first);
+
+ // Client B renews its lease.
+ clientB.setState(Dhcp4Client::RENEWING);
+ clientB.setDestAddress(IOAddress("10.0.0.1"));
+ ASSERT_NO_THROW(clientB.doRequest());
+ // The server should renew the client's B lease because the address
+ // reserved for client B is still in use by the client A.
+ ASSERT_TRUE(clientB.getContext().response_);
+ EXPECT_EQ(DHCPACK, static_cast<int>(clientB.getContext().response_->getType()));
+ ASSERT_NE(clientB.config_.lease_.addr_, in_pool_addr);
+ ASSERT_EQ(client_b_addr, clientB.config_.lease_.addr_);
+
+ // Client A performs 4-way exchange.
+ client.setState(Dhcp4Client::SELECTING);
+ // Revert to the broadcast address for the selecting client.
+ client.setDestAddress(IOAddress::IPV4_BCAST_ADDRESS());
+ // Obtain a lease from the server using the 4-way exchange.
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("0.0.0.0"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // The server should have assigned a different address than the one
+ // reserved for the Client B.
+ ASSERT_NE(client.config_.lease_.addr_, in_pool_addr);
+ subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->
+ selectSubnet(client.config_.lease_.addr_);
+ ASSERT_TRUE(subnet);
+ ASSERT_TRUE(subnet->inPool(Lease::TYPE_V4, client.config_.lease_.addr_));
+
+ // Client B renews again.
+ ASSERT_NO_THROW(clientB.doRequest());
+ // The client B should now receive the DHCPNAK from the server because
+ // the reserved address is now available and the client should
+ // revert to the DHCPDISCOVER to obtain it.
+ ASSERT_TRUE(clientB.getContext().response_);
+ EXPECT_EQ(DHCPNAK, static_cast<int>(clientB.getContext().response_->getType()));
+
+ // Client B performs 4-way exchange and obtains a lease for the
+ // reserved address.
+ clientB.setState(Dhcp4Client::SELECTING);
+ ASSERT_NO_THROW(clientB.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("0.0.0.0"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(clientB.getContext().response_);
+ resp = clientB.getContext().response_;
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ ASSERT_EQ(in_pool_addr, clientB.config_.lease_.addr_);
+}
+
+TEST_F(DORATest, reservationsWithConflicts) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ reservationsWithConflicts();
+}
+
+TEST_F(DORATest, reservationsWithConflictsMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ reservationsWithConflicts();
+}
+
+void
+DORATest::reservationModeDisabled() {
+ // Client has a reservation.
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Set explicit HW address so as it matches the reservation in the
+ // configuration used below.
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+ // Configure DHCP server. In this configuration the reservations flags are
+ // set to false. Thus, the server should ignore the reservation for
+ // this client.
+ configure(DORA_CONFIGS[12], *client.getServer());
+ // Client requests the 10.0.0.50 address and the server should assign it
+ // as it ignores the reservation in the current mode.
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("10.0.0.50"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Check that the requested IP address was assigned.
+ ASSERT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
+
+ // Reconfigure the server to respect the host reservations.
+ configure(DORA_CONFIGS[11], *client.getServer());
+
+ // The client requests the previously allocated address again, but the
+ // server should allocate the reserved address this time.
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("10.0.0.50"))));
+ // Check that the reserved IP address has been assigned.
+ ASSERT_EQ("10.0.0.65", client.config_.lease_.addr_.toText());
+}
+
+TEST_F(DORATest, reservationModeDisabled) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ reservationModeDisabled();
+}
+
+TEST_F(DORATest, reservationModeDisabledMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ reservationModeDisabled();
+}
+
+void
+DORATest::reservationIgnoredInDisabledMode() {
+ // Client has a reservation.
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Set MAC address which doesn't match the reservation configured.
+ client.setHWAddress("11:22:33:44:55:66");
+ // Configure DHCP server. In this configuration the reservations flags are
+ // disabled. Any client should be able to hijack the reserved address.
+ configure(DORA_CONFIGS[13], *client.getServer());
+ // Client requests the 10.0.0.65 address reserved for another client.
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("10.0.0.65"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Check that the address was hijacked.
+ ASSERT_EQ("10.0.0.65", client.config_.lease_.addr_.toText());
+}
+
+TEST_F(DORATest, reservationIgnoredInDisabledMode) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ reservationIgnoredInDisabledMode();
+}
+
+TEST_F(DORATest, reservationIgnoredInDisabledModeMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ reservationIgnoredInDisabledMode();
+}
+
+void
+DORATest::reservationModeOutOfPool() {
+ // Create the first client for which we have a reservation out of the
+ // dynamic pool.
+ Dhcp4Client clientA(Dhcp4Client::SELECTING);
+ clientA.setHWAddress("aa:bb:cc:dd:ee:ff");
+ // Configure the server to respect out of the pool reservations.
+ configure(DORA_CONFIGS[13], *clientA.getServer());
+ // The client for which we have a reservation is doing 4-way exchange
+ // and requests a different address than reserved. The server should
+ // allocate the reserved address to this client.
+ ASSERT_NO_THROW(clientA.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("10.0.0.40"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(clientA.getContext().response_);
+ Pkt4Ptr resp = clientA.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Check that the server allocated the reserved address.
+ ASSERT_EQ("10.0.0.200", clientA.config_.lease_.addr_.toText());
+
+ // Create another client which has a reservation within the pool.
+ // The server should ignore this reservation in the current mode.
+ Dhcp4Client clientB(clientA.getServer(), Dhcp4Client::SELECTING);
+ clientB.setHWAddress("11:22:33:44:55:66");
+ // This client is requesting a different address than reserved. The
+ // server should allocate this address to the client.
+ ASSERT_NO_THROW(clientB.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("10.0.0.40"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(clientB.getContext().response_);
+ resp = clientB.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Check that the requested address was assigned.
+ ASSERT_EQ("10.0.0.40", clientB.config_.lease_.addr_.toText());
+}
+
+TEST_F(DORATest, reservationModeOutOfPool) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ reservationModeOutOfPool();
+}
+
+TEST_F(DORATest, reservationModeOutOfPoolMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ reservationModeOutOfPool();
+}
+
+void
+DORATest::reservationIgnoredInOutOfPoolMode() {
+ // Create the first client for which we have a reservation out of the
+ // dynamic pool.
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.setHWAddress("12:34:56:78:9A:BC");
+ // Configure the server to respect out of the pool reservations only.
+ configure(DORA_CONFIGS[14], *client.getServer());
+ // The client which doesn't have a reservation is trying to hijack
+ // the reserved address and it should succeed.
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("10.0.0.65"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Check that the server allocated the requested address.
+ ASSERT_EQ("10.0.0.65", client.config_.lease_.addr_.toText());
+}
+
+TEST_F(DORATest, reservationIgnoredInOutOfPoolMode) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ reservationIgnoredInOutOfPoolMode();
+}
+
+TEST_F(DORATest, reservationIgnoredInOutOfPoolModeMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ reservationIgnoredInOutOfPoolMode();
+}
+
+void
+DORATest::statisticsDORA() {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[0], *client.getServer());
+
+ // Perform 4-way exchange with the server but to not request any
+ // specific address in the DHCPDISCOVER message.
+ ASSERT_NO_THROW(client.doDORA());
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Ok, let's check the statistics.
+ using namespace isc::stats;
+ StatsMgr& mgr = StatsMgr::instance();
+ ObservationPtr pkt4_received = mgr.getObservation("pkt4-received");
+ ObservationPtr pkt4_discover_received = mgr.getObservation("pkt4-discover-received");
+ ObservationPtr pkt4_offer_sent = mgr.getObservation("pkt4-offer-sent");
+ ObservationPtr pkt4_request_received = mgr.getObservation("pkt4-request-received");
+ ObservationPtr pkt4_ack_sent = mgr.getObservation("pkt4-ack-sent");
+ ObservationPtr pkt4_sent = mgr.getObservation("pkt4-sent");
+
+ // All expected statistics must be present.
+ ASSERT_TRUE(pkt4_received);
+ ASSERT_TRUE(pkt4_discover_received);
+ ASSERT_TRUE(pkt4_offer_sent);
+ ASSERT_TRUE(pkt4_request_received);
+ ASSERT_TRUE(pkt4_ack_sent);
+ ASSERT_TRUE(pkt4_sent);
+
+ // They also must have expected values.
+ EXPECT_EQ(2, pkt4_received->getInteger().first);
+ EXPECT_EQ(1, pkt4_discover_received->getInteger().first);
+ EXPECT_EQ(1, pkt4_offer_sent->getInteger().first);
+ EXPECT_EQ(1, pkt4_request_received->getInteger().first);
+ EXPECT_EQ(1, pkt4_ack_sent->getInteger().first);
+ EXPECT_EQ(2, pkt4_sent->getInteger().first);
+
+ // Let the client send request 3 times, which should make the server
+ // to send 3 acks.
+ client.setState(Dhcp4Client::RENEWING);
+ ASSERT_NO_THROW(client.doRequest());
+ ASSERT_NO_THROW(client.doRequest());
+ ASSERT_NO_THROW(client.doRequest());
+
+ // Let's see if the stats are properly updated.
+ EXPECT_EQ(5, pkt4_received->getInteger().first);
+ EXPECT_EQ(1, pkt4_discover_received->getInteger().first);
+ EXPECT_EQ(1, pkt4_offer_sent->getInteger().first);
+ EXPECT_EQ(4, pkt4_request_received->getInteger().first);
+ EXPECT_EQ(4, pkt4_ack_sent->getInteger().first);
+ EXPECT_EQ(5, pkt4_sent->getInteger().first);
+}
+
+TEST_F(DORATest, statisticsDORA) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ statisticsDORA();
+}
+
+TEST_F(DORATest, statisticsDORAMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ statisticsDORA();
+}
+
+void
+DORATest::statisticsNAK() {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[0], *client.getServer());
+ // Obtain a lease from the server using the 4-way exchange.
+
+ // Get a lease.
+ client.doDORA();
+
+ // Wipe all stats.
+ isc::stats::StatsMgr::instance().removeAll();
+
+ client.setState(Dhcp4Client::INIT_REBOOT);
+ client.config_.lease_.addr_ = IOAddress("10.0.0.30");
+ ASSERT_NO_THROW(client.doRequest());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ EXPECT_EQ(DHCPNAK, static_cast<int>(resp->getType()));
+
+ using namespace isc::stats;
+ StatsMgr& mgr = StatsMgr::instance();
+ ObservationPtr pkt4_received = mgr.getObservation("pkt4-received");
+ ObservationPtr pkt4_request_received = mgr.getObservation("pkt4-request-received");
+ ObservationPtr pkt4_ack_sent = mgr.getObservation("pkt4-ack-sent");
+ ObservationPtr pkt4_nak_sent = mgr.getObservation("pkt4-nak-sent");
+ ObservationPtr pkt4_sent = mgr.getObservation("pkt4-sent");
+
+ // All expected statistics must be present.
+ ASSERT_TRUE(pkt4_received);
+ ASSERT_TRUE(pkt4_request_received);
+ ASSERT_FALSE(pkt4_ack_sent); // No acks were sent, no such statistic expected.
+ ASSERT_TRUE(pkt4_nak_sent);
+ ASSERT_TRUE(pkt4_sent);
+
+ // They also must have expected values.
+ EXPECT_EQ(1, pkt4_received->getInteger().first);
+ EXPECT_EQ(1, pkt4_request_received->getInteger().first);
+ EXPECT_EQ(1, pkt4_nak_sent->getInteger().first);
+ EXPECT_EQ(1, pkt4_sent->getInteger().first);
+}
+
+TEST_F(DORATest, statisticsNAK) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ statisticsNAK();
+}
+
+TEST_F(DORATest, statisticsNAKMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ statisticsNAK();
+}
+
+void
+DORATest::testMultiStageBoot(const unsigned int config_index) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Configure DHCP server.
+ ASSERT_NO_THROW(configure(DORA_CONFIGS[config_index],
+ *client.getServer()));
+
+ // Stage 1: get the first lease for our client. In PXE boot, it would be
+ // a stage when the BIOS requests a lease.
+
+ // Include client id apart from the MAC address.
+ client.includeClientId("10:21:32:AB:CD:EF");
+
+ ASSERT_NO_THROW(client.doDORA());
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Make sure that the client has got the lease which belongs
+ // to a pool.
+ IOAddress leased_address1 = client.config_.lease_.addr_;
+ Subnet4Ptr subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->
+ selectSubnet(leased_address1);
+ ASSERT_TRUE(subnet);
+ // Make sure that the address has been allocated from the dynamic pool.
+ ASSERT_TRUE(subnet->inPool(Lease::TYPE_V4, leased_address1));
+
+ // Stage 2: the client with a given MAC address has a lease in the
+ // lease database. The installer comes up and uses the same MAC address
+ // but generates a different client id. The server should treat the
+ // client with modified client identifier as a different client and
+ // create a new lease for it.
+
+ // Modify client identifier.
+ client.includeClientId("11:54:45:AB:AA:FE");
+
+ ASSERT_NO_THROW(client.doDORA());
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Make sure that the client has got the lease which belongs
+ // to a pool.
+ IOAddress leased_address2 = client.config_.lease_.addr_;
+ // Make sure that the address has been allocated from the dynamic pool.
+ ASSERT_TRUE(subnet->inPool(Lease::TYPE_V4, leased_address2));
+
+ // The client should have got a new lease.
+ ASSERT_NE(leased_address1, leased_address2);
+
+ // Modify client identifier again.
+ client.includeClientId("22:34:AC:BE:44:54");
+
+ ASSERT_NO_THROW(client.doDORA());
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Make sure that the client has got the lease which belongs
+ // to a pool.
+ IOAddress leased_address3 = client.config_.lease_.addr_;
+ // Make sure that the address has been allocated from the dynamic pool.
+ ASSERT_TRUE(subnet->inPool(Lease::TYPE_V4, leased_address3));
+
+ // The client should have got a new lease.
+ ASSERT_NE(leased_address1, leased_address3);
+ ASSERT_NE(leased_address2, leased_address3);
+}
+
+// Test that the client using the same hardware address but multiple
+// client identifiers will obtain multiple leases.
+TEST_F(DORATest, multiStageBoot) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ // DORA_CONFIGS[0] to be used for server configuration.
+ testMultiStageBoot(0);
+}
+
+// Test that the client using the same hardware address but multiple
+// client identifiers will obtain multiple leases.
+TEST_F(DORATest, multiStageBootMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ // DORA_CONFIGS[0] to be used for server configuration.
+ testMultiStageBoot(0);
+}
+
+void
+DORATest::customServerIdentifier() {
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ // Configure DHCP server.
+ ASSERT_NO_THROW(configure(DORA_CONFIGS[7], *client1.getServer()));
+
+ ASSERT_NO_THROW(client1.doDORA());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client1.getContext().response_);
+ Pkt4Ptr resp = client1.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // The explicitly configured server identifier should take precedence
+ // over generated server identifier.
+ EXPECT_EQ("1.2.3.4", client1.config_.serverid_.toText());
+
+ // Repeat the test for different subnet.
+ Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+ client2.setIfaceName("eth1");
+ client2.setIfaceIndex(ETH1_INDEX);
+
+ ASSERT_NO_THROW(client2.doDORA());
+ ASSERT_TRUE(client2.getContext().response_);
+ resp = client2.getContext().response_;
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ EXPECT_EQ("2.3.4.5", client2.config_.serverid_.toText());
+
+ // Create relayed client which will be assigned a lease from the third
+ // subnet. This subnet inherits server identifier value from the global
+ // scope.
+ Dhcp4Client client3(client1.getServer(), Dhcp4Client::SELECTING);
+ client3.useRelay(true, IOAddress("10.2.3.4"));
+
+ ASSERT_NO_THROW(client3.doDORA());
+ ASSERT_TRUE(client3.getContext().response_);
+ resp = client3.getContext().response_;
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ EXPECT_EQ("3.4.5.6", client3.config_.serverid_.toText());
+}
+
+TEST_F(DORATest, customServerIdentifier) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ customServerIdentifier();
+}
+
+TEST_F(DORATest, customServerIdentifierMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ customServerIdentifier();
+}
+
+void
+DORATest::changingCircuitId() {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+ // Use relay agent so as the circuit-id can be inserted.
+ client.useRelay(true, IOAddress("10.0.0.1"), IOAddress("10.0.0.2"));
+
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[10], *client.getServer());
+
+ // Send DHCPDISCOVER.
+ boost::shared_ptr<IOAddress> requested_address(new IOAddress("10.0.0.9"));
+ ASSERT_NO_THROW(client.doDiscover(requested_address));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPOFFER
+ ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+ // Make sure that the client has been offered a different address
+ // given that circuit-id is not used.
+ EXPECT_NE("10.0.0.9", resp->getYiaddr().toText());
+
+ // Specify circuit-id matching the one in the configuration.
+ client.setCircuitId("charter950");
+
+ // Send DHCPDISCOVER.
+ ASSERT_NO_THROW(client.doDiscover());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPOFFER
+ ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+ // Make sure that the client has been offered reserved address given that
+ // matching circuit-id has been specified.
+ EXPECT_EQ("10.0.0.9", resp->getYiaddr().toText());
+
+ // Let's now change the circuit-id.
+ client.setCircuitId("gdansk");
+
+ // The client requests offered address but should be refused this address
+ // given that the circuit-id is not matching.
+ ASSERT_NO_THROW(client.doRequest());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ // The client should be refused this address.
+ EXPECT_EQ(DHCPNAK, static_cast<int>(resp->getType()));
+
+ // In this case, the client falls back to the 4-way exchange and should be
+ // allocated an address from the dynamic pool.
+ ASSERT_NO_THROW(client.doDORA());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ resp = client.getContext().response_;
+ // The client should be allocated some address.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ EXPECT_NE("10.0.0.9", client.config_.lease_.addr_.toText());
+}
+
+TEST_F(DORATest, changingCircuitId) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ changingCircuitId();
+}
+
+TEST_F(DORATest, changingCircuitIdMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ changingCircuitId();
+}
+
+void
+DORATest::storeExtendedInfoEnabled() {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Use relay agent to make sure that the desired subnet is
+ // selected for our client.
+ client.useRelay(true, IOAddress("10.0.0.20"), IOAddress("10.0.0.21"));
+ // Specify client identifier.
+ client.includeClientId("01:11:22:33:44:55:66");
+ // Set the circuit id to make relay-agent-info more interesting.
+ client.setCircuitId("charter950");
+
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[16], *client.getServer());
+ // Client A performs 4-way exchange and should obtain a reserved
+ // address.
+ ASSERT_NO_THROW_LOG(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("0.0.0.0"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Make sure that the client has got the lease for the reserved address.
+ ASSERT_EQ("10.0.0.10", client.config_.lease_.addr_.toText());
+
+ // The stored lease should have extended info.
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(client.config_.lease_.addr_);
+ ASSERT_TRUE(lease);
+ ASSERT_TRUE(lease->getContext());
+
+ // Make user the user-context content is correct.
+ std::string expected_context = "{ \"ISC\": { \"relay-agent-info\":"
+ " \"0x010A63686172746572393530\" } }";
+ stringstream ss;
+ ss << *(lease->getContext());
+ ASSERT_EQ(expected_context, ss.str());
+}
+
+TEST_F(DORATest, storeExtendedInfoEnabled) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ storeExtendedInfoEnabled();
+}
+
+TEST_F(DORATest, storeExtendedInfoEnabledMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ storeExtendedInfoEnabled();
+}
+
+void
+DORATest::storeExtendedInfoDisabled() {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Use relay agent to make sure that the desired subnet is
+ // selected for our client.
+ client.useRelay(true, IOAddress("192.0.2.20"), IOAddress("192.0.2.21"));
+ // Specify client identifier.
+ client.includeClientId("01:11:22:33:44:55:66");
+ // Set the circuit id to make relay-agent-info more interesting.
+ client.setCircuitId("charter950");
+
+ // Configure DHCP server.
+ configure(DORA_CONFIGS[16], *client.getServer());
+ // Client A performs 4-way exchange and should obtain a reserved
+ // address.
+ ASSERT_NO_THROW_LOG(client.doDORA(boost::shared_ptr<
+ IOAddress>(new IOAddress("0.0.0.0"))));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Make sure that the client has got the lease for the reserved address.
+ ASSERT_EQ("192.0.2.10", client.config_.lease_.addr_.toText());
+
+ // The stored lease should not have extended info.
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(client.config_.lease_.addr_);
+ ASSERT_TRUE(lease);
+ ASSERT_FALSE(lease->getContext());
+}
+
+TEST_F(DORATest, storeExtendedInfoDisabled) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ storeExtendedInfoDisabled();
+}
+
+TEST_F(DORATest, storeExtendedInfoDisabledMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ storeExtendedInfoDisabled();
+}
+
+// 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 test utilizing MySQL database backend.
+class DORAMySQLTest : public DORATest {
+public:
+ /// @brief Constructor.
+ ///
+ /// Recreates MySQL schema for a test.
+ DORAMySQLTest() : DORATest() {
+ // Ensure we have the proper schema with no transient data.
+ db::test::createMySQLSchema();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Destroys MySQL schema.
+ virtual ~DORAMySQLTest() {
+ // If data wipe enabled, delete transient data otherwise destroy the schema.
+ db::test::destroyMySQLSchema();
+ }
+};
+
+// Test that the client using the same hardware address but multiple
+// client identifiers will obtain multiple leases (MySQL lease database).
+TEST_F(DORAMySQLTest, multiStageBoot) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ // DORA_CONFIGS[8] to be used for server configuration.
+ testMultiStageBoot(8);
+}
+
+// Test that the client using the same hardware address but multiple
+// client identifiers will obtain multiple leases (MySQL lease database).
+TEST_F(DORAMySQLTest, multiStageBootMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ // DORA_CONFIGS[8] to be used for server configuration.
+ testMultiStageBoot(8);
+}
+
+#endif
+
+// Starting tests which require MySQL backend availability. Those tests
+// will not be executed if Kea has been compiled without the
+// --with-pgsql.
+#ifdef HAVE_PGSQL
+
+/// @brief Test fixture class for the test utilizing PostgreSQL database backend.
+class DORAPgSQLTest : public DORATest {
+public:
+ /// @brief Constructor.
+ ///
+ /// Recreates PgSQL schema for a test.
+ DORAPgSQLTest() : DORATest() {
+ // Ensure we have the proper schema with no transient data.
+ db::test::createPgSQLSchema();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Destroys PgSQL schema.
+ virtual ~DORAPgSQLTest() {
+ // If data wipe enabled, delete transient data otherwise destroy the schema
+ db::test::destroyPgSQLSchema();
+ }
+};
+
+// Test that the client using the same hardware address but multiple
+// client identifiers will obtain multiple leases (PostgreSQL lease database).
+TEST_F(DORAPgSQLTest, multiStageBoot) {
+ Dhcpv4SrvMTTestGuard guard(*this, false);
+ // DORA_CONFIGS[9] to be used for server configuration.
+ testMultiStageBoot(9);
+}
+
+// Test that the client using the same hardware address but multiple
+// client identifiers will obtain multiple leases (PostgreSQL lease database).
+TEST_F(DORAPgSQLTest, multiStageBootMultiThreading) {
+ Dhcpv4SrvMTTestGuard guard(*this, true);
+ // DORA_CONFIGS[9] to be used for server configuration.
+ testMultiStageBoot(9);
+}
+
+#endif
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/fqdn_unittest.cc b/src/bin/dhcp4/tests/fqdn_unittest.cc
new file mode 100644
index 0000000..2a275c6
--- /dev/null
+++ b/src/bin/dhcp4/tests/fqdn_unittest.cc
@@ -0,0 +1,2644 @@
+// Copyright (C) 2013-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 <asiolink/io_address.h>
+#include <dhcp/option4_client_fqdn.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp4/tests/dhcp4_client.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp_ddns/ncr_msg.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/ncr_generator.h>
+#include <testutils/gtest_utils.h>
+
+#include <gtest/gtest.h>
+#include <boost/scoped_ptr.hpp>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::dhcp_ddns;
+
+namespace {
+
+/// @brief Set of JSON configurations used by the FQDN tests.
+const char* CONFIGS[] = {
+ // 0
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 3000,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"routers\","
+ " \"data\": \"10.0.0.200,10.0.0.201\""
+ " } ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"hostname\": \"unique-host.example.com\""
+ " }"
+ " ]"
+ " }],"
+ "\"dhcp-ddns\": {"
+ "\"enable-updates\": true,"
+ "\"qualifying-suffix\": \"\""
+ "}"
+ "}",
+ // 1
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 3000,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"routers\","
+ " \"data\": \"10.0.0.200,10.0.0.201\""
+ " } ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"hostname\": \"foobar\""
+ " }"
+ " ]"
+ " }],"
+ "\"dhcp-ddns\": {"
+ "\"enable-updates\": true,"
+ "\"qualifying-suffix\": \"fake-suffix.isc.org.\""
+ "}"
+ "}",
+ // 2
+ // Simple config with DDNS updates disabled. Note pool is one address
+ // large to ensure we get a specific address back.
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 3000,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.10\" } ]"
+ " }],"
+ "\"dhcp-ddns\": {"
+ "\"enable-updates\": false,"
+ "\"qualifying-suffix\": \"fake-suffix.isc.org.\""
+ "}"
+ "}",
+ // 3
+ // Simple config with DDNS updates enabled. Note pool is one address
+ // large to ensure we get a specific address back.
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 3000,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.10\" } ]"
+ " }],"
+ "\"dhcp-ddns\": {"
+ "\"enable-updates\": true,"
+ "\"qualifying-suffix\": \"fake-suffix.isc.org.\""
+ "}"
+ "}",
+ // 4
+ // Configuration which disables DNS updates but contains a reservation
+ // for a hostname. Reserved hostname should be assigned to a client if
+ // the client includes it in the Parameter Request List option.
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 3000,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"routers\","
+ " \"data\": \"10.0.0.200,10.0.0.201\""
+ " } ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"hostname\": \"reserved.example.com\""
+ " }"
+ " ]"
+ " }],"
+ "\"dhcp-ddns\": {"
+ "\"enable-updates\": false,"
+ "\"qualifying-suffix\": \"\""
+ "}"
+ "}",
+ // 5
+ // Configuration which disables DNS updates but contains a reservation
+ // for a hostname and the qualifying-suffix which should be appended to
+ // the reserved hostname in the Hostname option returned to a client.
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 3000,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"routers\","
+ " \"data\": \"10.0.0.200,10.0.0.201\""
+ " } ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"hostname\": \"foo-bar\""
+ " }"
+ " ]"
+ " }],"
+ "\"dhcp-ddns\": {"
+ "\"enable-updates\": false,"
+ "\"qualifying-suffix\": \"example.isc.org\""
+ "}"
+ "}",
+ // 6
+ // Configuration which enables DNS updates and hostname sanitization
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 3000,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"routers\","
+ " \"data\": \"10.0.0.200,10.0.0.201\""
+ " } ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"hostname\": \"unique-xxx-host.example.com\""
+ " }"
+ " ]"
+ " }],"
+ "\"dhcp-ddns\": {"
+ "\"enable-updates\": true,"
+ "\"hostname-char-set\" : \"[^A-Za-z0-9.-]\","
+ "\"hostname-char-replacement\" : \"x\","
+ "\"qualifying-suffix\": \"example.com\""
+ "}"
+ "}",
+ // 7
+ // Configuration with disabled DNS updates (default) and
+ // hostname sanitization defined at global scope.
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 3000,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"routers\","
+ " \"data\": \"10.0.0.200,10.0.0.201\""
+ " } ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"hostname\": \"unique-xxx-host.example.com\""
+ " }"
+ " ]"
+ " }],"
+ "\"hostname-char-set\" : \"[^A-Za-z0-9.-]\","
+ "\"hostname-char-replacement\" : \"x\""
+ "}",
+ // 8
+ // D2 enabled
+ // global ddns-send-updates is false
+ // one subnet does not enable updates
+ // one subnet does enables updates
+ "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\n"
+ "\"dhcp-ddns\": {\n"
+ "\"enable-updates\": true\n"
+ "},\n"
+ "\"ddns-send-updates\": false,\n"
+ "\"subnet4\": [ {\n"
+ " \"subnet\": \"192.0.2.0/24\",\n"
+ " \"id\": 1,\n"
+ " \"pools\": [ { \"pool\": \"192.0.2.10-192.0.2.100\" } ],\n"
+ " \"interface\": \"eth0\"\n"
+ " },\n"
+ " {\n"
+ " \"subnet\": \"192.0.3.0/24\", \n"
+ " \"id\": 2,\n"
+ " \"pools\": [ { \"pool\": \"192.0.3.10-192.0.3.100\" } ],\n"
+ " \"interface\": \"eth1\",\n"
+ " \"ddns-send-updates\": true\n"
+ " }\n"
+ "]\n"
+ "}",
+ // 9
+ // Simple config with DDNS updates enabled. Note pool is one address
+ // large to ensure we get a specific address back.
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 3000,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"192.0.2.10-192.0.2.10\" } ]"
+ " }],"
+ "\"dhcp-ddns\": {"
+ "\"enable-updates\": true,"
+ "\"qualifying-suffix\": \"fake-suffix.isc.org.\""
+ "}"
+ "}",
+ // 10
+ // D2 enabled
+ // shared-network with two subnets with
+ // different DDNS parameters
+ "{ \"interfaces-config\": { \n"
+ " \"interfaces\": [ \"*\" ] \n"
+ " }, \n"
+ " \"dhcp-ddns\": { \n"
+ " \"enable-updates\": true \n"
+ " }, \n"
+ " \"shared-networks\": [ \n"
+ " { \n"
+ " \"interface\": \"eth1\", \n"
+ " \"name\": \"foo\", \n"
+ " \"subnet4\": [ { \n"
+ " \"subnet\": \"192.0.2.0/24\", \n"
+ " \"id\": 1, \n"
+ " \"pools\": [ { \n"
+ " \"pool\": \"192.0.2.10 - 192.0.2.10\" \n"
+ " }], \n"
+ " \"ddns-qualifying-suffix\": \"one.example.com.\" \n"
+ " }, \n"
+ " { \n"
+ " \"subnet\": \"10.0.0.0/24\", \n"
+ " \"id\": 2, \n"
+ " \"pools\": [ { \n"
+ " \"pool\": \"10.0.0.10 - 10.0.0.10\" \n"
+ " }], \n"
+ " \"ddns-qualifying-suffix\": \"two.example.com.\" \n"
+ " }] \n"
+ " }] \n"
+ "}"
+};
+
+class NameDhcpv4SrvTest : public Dhcpv4SrvTest {
+public:
+ // Reference to D2ClientMgr singleton
+ D2ClientMgr& d2_mgr_;
+
+ /// @brief Pointer to the DHCP server instance.
+ boost::shared_ptr<NakedDhcpv4Srv> srv_;
+
+ /// @brief Interface Manager's fake configuration control.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+
+ // Bit Constants for turning on and off DDNS configuration options.
+ 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
+ };
+
+ NameDhcpv4SrvTest()
+ : Dhcpv4SrvTest(),
+ d2_mgr_(CfgMgr::instance().getD2ClientMgr()),
+ iface_mgr_test_config_(true)
+ {
+ srv_ = boost::make_shared<NakedDhcpv4Srv>(0);
+ IfaceMgr::instance().openSockets4();
+ // Config DDNS to be enabled, all controls off
+ enableD2();
+ }
+
+ virtual ~NameDhcpv4SrvTest() {
+ // CfgMgr singleton doesn't get wiped between tests, so we'll
+ // disable D2 explicitly between tests.
+ disableD2();
+ }
+
+ /// @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("127.0.0.1"), 53001,
+ isc::asiolink::IOAddress("0.0.0.0"), 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_->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_));
+ }
+
+ // Create a lease to be used by various tests.
+ Lease4Ptr createLease(const isc::asiolink::IOAddress& addr,
+ const std::string& hostname,
+ const bool fqdn_fwd,
+ const bool fqdn_rev) {
+ const uint8_t hwaddr_data[] = { 0, 1, 2, 3, 4, 5, 6 };
+ HWAddrPtr hwaddr(new HWAddr(hwaddr_data, sizeof(hwaddr_data),
+ HTYPE_ETHER));
+ Lease4Ptr lease(new Lease4(addr, hwaddr,
+ &generateClientId()->getData()[0],
+ generateClientId()->getData().size(),
+ 100, time(NULL), subnet_->getID()));
+ // @todo Set this through the Lease4 constructor.
+ lease->hostname_ = hostname;
+ lease->fqdn_fwd_ = fqdn_fwd;
+ lease->fqdn_rev_ = fqdn_rev;
+
+ return (lease);
+ }
+
+ // Create an instance of the DHCPv4 Client FQDN Option.
+ Option4ClientFqdnPtr
+ createClientFqdn(const uint8_t flags,
+ const std::string& fqdn_name,
+ Option4ClientFqdn::DomainNameType fqdn_type) {
+ return (Option4ClientFqdnPtr(new Option4ClientFqdn(flags,
+ Option4ClientFqdn::
+ RCODE_CLIENT(),
+ fqdn_name,
+ fqdn_type)));
+ }
+
+ // Create an instance of the Hostname option.
+ OptionStringPtr
+ createHostname(const std::string& hostname) {
+ OptionStringPtr opt_hostname(new OptionString(Option::V4,
+ DHO_HOST_NAME,
+ hostname));
+ return (opt_hostname);
+ }
+
+ /// @brief Convenience method for generating an FQDN from an IP address.
+ ///
+ /// This is just a wrapper method around the D2ClientMgr's method for
+ /// generating domain names from the configured prefix, suffix, and a
+ /// given IP address. This is useful for verifying that fully generated
+ /// names are correct.
+ ///
+ /// @param addr IP address used in the lease.
+ /// @param trailing_dot A boolean flag which indicates whether the
+ /// trailing dot should be appended to the end of the hostname.
+ /// The default value is "true" which means that it should.
+ ///
+ /// @return An std::string contained the generated FQDN.
+ std::string generatedNameFromAddress(const IOAddress& addr,
+ const bool trailing_dot = true) {
+ return(CfgMgr::instance().getD2ClientMgr()
+ .generateFqdn(addr, *getDdnsParams(), trailing_dot));
+ }
+
+ // Get the Client FQDN Option from the given message.
+ Option4ClientFqdnPtr getClientFqdnOption(const Pkt4Ptr& pkt) {
+ return (boost::dynamic_pointer_cast<
+ Option4ClientFqdn>(pkt->getOption(DHO_FQDN)));
+ }
+
+ // get the Hostname option from the given message.
+ OptionStringPtr getHostnameOption(const Pkt4Ptr& pkt) {
+ return (boost::dynamic_pointer_cast<
+ OptionString>(pkt->getOption(DHO_HOST_NAME)));
+ }
+
+ // Create a message holding DHCPv4 Client FQDN Option.
+ Pkt4Ptr generatePktWithFqdn(const uint8_t msg_type,
+ const uint8_t fqdn_flags,
+ const std::string& fqdn_domain_name,
+ Option4ClientFqdn::DomainNameType fqdn_type,
+ const bool include_prl,
+ const bool include_clientid = true) {
+ Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(msg_type, 1234));
+ pkt->setRemoteAddr(IOAddress("192.0.2.3"));
+ pkt->setIface("eth1");
+ pkt->setIndex(ETH1_INDEX);
+ // For DISCOVER we don't include server id, because client broadcasts
+ // the message to all servers.
+ if (msg_type != DHCPDISCOVER) {
+ pkt->addOption(srv_->getServerID());
+ }
+
+ if (include_clientid) {
+ pkt->addOption(generateClientId());
+ }
+
+ // Create Client FQDN Option with the specified flags and
+ // domain-name.
+ pkt->addOption(createClientFqdn(fqdn_flags, fqdn_domain_name,
+ fqdn_type));
+
+ // Control whether or not to request that server returns the FQDN
+ // option. Server may be configured to always return it or return
+ // only in case client requested it.
+ if (include_prl) {
+ OptionUint8ArrayPtr option_prl =
+ OptionUint8ArrayPtr(new OptionUint8Array(Option::V4,
+ DHO_DHCP_PARAMETER_REQUEST_LIST));
+ option_prl->addValue(DHO_FQDN);
+ }
+ return (pkt);
+ }
+
+ // Create a message holding a Hostname option.
+ Pkt4Ptr generatePktWithHostname(const uint8_t msg_type,
+ const std::string& hostname) {
+
+ Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(msg_type, 1234));
+ pkt->setRemoteAddr(IOAddress("192.0.2.3"));
+ // For DISCOVER we don't include server id, because client broadcasts
+ // the message to all servers.
+ if (msg_type != DHCPDISCOVER) {
+ pkt->addOption(srv_->getServerID());
+ }
+
+ pkt->addOption(generateClientId());
+
+
+ // Create Client FQDN Option with the specified flags and
+ // domain-name.
+ pkt->addOption(createHostname(hostname));
+
+ return (pkt);
+
+ }
+
+ // Create a message holding an empty Hostname option.
+ Pkt4Ptr generatePktWithEmptyHostname(const uint8_t msg_type) {
+
+ Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(msg_type, 1234));
+ pkt->setRemoteAddr(IOAddress("192.0.2.3"));
+ // For DISCOVER we don't include server id, because client broadcasts
+ // the message to all servers.
+ if (msg_type != DHCPDISCOVER) {
+ pkt->addOption(srv_->getServerID());
+ }
+
+ pkt->addOption(generateClientId());
+
+ // Create Hostname option.
+ std::string hostname(" ");
+ OptionPtr opt = createHostname(hostname);
+ opt->setData(hostname.begin(), hostname.begin());
+ pkt->addOption(opt);
+
+ return (pkt);
+
+ }
+
+ // Create a message holding of a given type
+ Pkt4Ptr generatePkt(const uint8_t msg_type) {
+ Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(msg_type, 1234));
+ pkt->setRemoteAddr(IOAddress("192.0.2.3"));
+ // For DISCOVER we don't include server id, because client broadcasts
+ // the message to all servers.
+ if (msg_type != DHCPDISCOVER) {
+ pkt->addOption(srv_->getServerID());
+ }
+
+ pkt->addOption(generateClientId());
+ return (pkt);
+ }
+
+ // Test that server generates the appropriate FQDN option in response to
+ // client's FQDN option.
+ void testProcessFqdn(const Pkt4Ptr& query, const uint8_t exp_flags,
+ const std::string& exp_domain_name,
+ Option4ClientFqdn::DomainNameType
+ exp_domain_type = Option4ClientFqdn::FULL) {
+ ASSERT_TRUE(getClientFqdnOption(query));
+
+ Pkt4Ptr answer;
+ if (query->getType() == DHCPDISCOVER) {
+ answer.reset(new Pkt4(DHCPOFFER, 1234));
+
+ } else {
+ answer.reset(new Pkt4(DHCPACK, 1234));
+
+ }
+ Dhcpv4Exchange ex = createExchange(query);
+ ASSERT_NO_THROW(srv_->processClientName(ex));
+
+ Option4ClientFqdnPtr fqdn = getClientFqdnOption(ex.getResponse());
+ ASSERT_TRUE(fqdn);
+
+ checkFqdnFlags(ex.getResponse(), exp_flags);
+
+ EXPECT_EQ(exp_domain_name, fqdn->getDomainName());
+ EXPECT_EQ(exp_domain_type, fqdn->getDomainNameType());
+
+ }
+
+ // Test that the server's processes the hostname (or lack thereof)
+ // in a client request correctly, according to the replace-client-name
+ // mode configuration parameter. We include hostname sanitizer to ensure
+ // it does not interfere with name replacement.
+ //
+ // @param mode - value to use for replace-client-name
+ // @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 hostname 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\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 3000,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"192.0.2.10-192.0.2.10\" } ]"
+ " }],"
+ "\"dhcp-ddns\": {"
+ " \"enable-updates\": true,"
+ " \"qualifying-suffix\": \"fake-suffix.isc.org.\","
+ " \"hostname-char-set\": \"[^A-Za-z0-9.-]\","
+ " \"hostname-char-replacement\": \"x\","
+ " \"replace-client-name\": \"%s\""
+ "}}";
+
+ // Create the configuration and configure the server
+ char config_buf[1024];
+ sprintf(config_buf, config_template, mode);
+ ASSERT_NO_THROW(configure(config_buf, *srv_)) << "configuration failed";
+
+ // Build our client packet
+ Pkt4Ptr query;
+ if (client_name_flag == CLIENT_NAME_PRESENT) {
+ ASSERT_NO_THROW(query = generatePktWithHostname(DHCPREQUEST,
+ "my.example.com."));
+ } else {
+ ASSERT_NO_THROW(query = generatePkt(DHCPREQUEST));
+ }
+
+ // Run the packet through the server, extracting the hostname option
+ // from the response. If the option isn't present the returned pointer
+ // will be null.
+ OptionStringPtr hostname;
+ ASSERT_NO_THROW(
+ hostname = processHostname(query,
+ client_name_flag == CLIENT_NAME_PRESENT)
+ ) << "processHostname throw an exception";
+
+ // Verify the contents (or lack thereof) of the hostname
+ if (exp_replacement_flag == NAME_REPLACED) {
+ ASSERT_TRUE(hostname)
+ << "No host name, it should have the replacement name \".\"";
+ EXPECT_EQ(".", hostname->getValue());
+ } else {
+ if (client_name_flag == CLIENT_NAME_PRESENT) {
+ ASSERT_TRUE(hostname)
+ << "No host name, expected original from client";
+ EXPECT_EQ("my.example.com.", hostname->getValue());
+ } else {
+ ASSERT_FALSE(hostname)
+ << "Host name is: " << hostname
+ << ", it should have been null";
+ }
+ }
+ }
+
+ /// @brief Checks the packet's FQDN option flags against a given mask
+ ///
+ /// @param pkt IPv4 packet whose FQDN flags should be checked.
+ /// @param exp_flags Bit mask of flags that are expected to be true.
+ void checkFqdnFlags(const Pkt4Ptr& pkt, const uint8_t exp_flags) {
+ Option4ClientFqdnPtr fqdn = getClientFqdnOption(pkt);
+ ASSERT_TRUE(fqdn);
+
+ const bool flag_n = (exp_flags & Option4ClientFqdn::FLAG_N) != 0;
+ const bool flag_s = (exp_flags & Option4ClientFqdn::FLAG_S) != 0;
+ const bool flag_o = (exp_flags & Option4ClientFqdn::FLAG_O) != 0;
+ const bool flag_e = (exp_flags & Option4ClientFqdn::FLAG_E) != 0;
+
+ EXPECT_EQ(flag_n, fqdn->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ(flag_s, fqdn->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_EQ(flag_o, fqdn->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_EQ(flag_e, fqdn->getFlag(Option4ClientFqdn::FLAG_E));
+ }
+
+
+ /// @brief Invokes Dhcpv4Srv::processHostname on the given packet
+ ///
+ /// Processes the Hostname option in the client's message and returns
+ /// the hostname option which would be sent to the client. It will
+ /// return empty if the hostname option is not to be included
+ /// server's response.
+ /// @param query - client packet to process
+ /// @param must_have_host - flag indicating whether or not the client
+ /// packet must contain the hostname option
+ ///
+ /// @return a pointer to the hostname option constructed by the server
+ OptionStringPtr processHostname(const Pkt4Ptr& query,
+ bool must_have_host = true) {
+ if (!getHostnameOption(query) && must_have_host) {
+ ADD_FAILURE() << "Hostname option not carried in the query";
+ }
+
+ Pkt4Ptr answer;
+ if (query->getType() == DHCPDISCOVER) {
+ answer.reset(new Pkt4(DHCPOFFER, 1234));
+
+ } else {
+ answer.reset(new Pkt4(DHCPACK, 1234));
+
+ }
+
+ Dhcpv4Exchange ex = createExchange(query);
+ if (!ex.getContext()->subnet_) {
+ ADD_FAILURE() << "createExchange did not select a subnet";
+ }
+
+ srv_->processClientName(ex);
+
+ OptionStringPtr hostname = getHostnameOption(ex.getResponse());
+ return (hostname);
+
+ }
+
+ ///@brief Verify that NameChangeRequest holds valid values.
+ ///
+ /// Pulls the NCR from the top of the send queue and checks its content
+ /// against a number of expected parameters.
+ ///
+ /// @param type - expected NCR change type, CHG_ADD or CHG_REMOVE
+ /// @param reverse - flag indicating whether or not the NCR specifies
+ /// reverse change
+ /// @param forward - flag indication whether or not the NCR specifies
+ /// forward change
+ /// @param addr - expected lease address in the NCR
+ /// @param fqdn - expected FQDN in the NCR
+ /// @param dhcid - expected DHCID in the NCR (comparison is performed only
+ /// if the value supplied is not empty):w
+ /// @param cltt - cltt value from the lease the NCR for which the NCR
+ /// was generated expected value for
+ /// @param lifetime - lease's valid lifetime from which NCR ttl was
+ /// generated
+ /// @param not_strict_expire_check - when true the comparison of the NCR
+ /// lease expiration time is conducted as greater than or equal to rather
+ /// equal to CLTT plus lease ttl .
+ /// @param exp_use_cr expected value of conflict resolution flag
+ void verifyNameChangeRequest(const isc::dhcp_ddns::NameChangeType type,
+ const bool reverse, const bool forward,
+ const std::string& addr,
+ const std::string& fqdn,
+ const std::string& dhcid,
+ const time_t cltt,
+ const uint16_t valid_lft,
+ const bool not_strict_expire_check = false,
+ const bool exp_use_cr = true) {
+ 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());
+ EXPECT_EQ(fqdn, ncr->getFqdn());
+ // Compare dhcid if it is not empty. In some cases, the DHCID is
+ // not known in advance and can't be compared.
+ if (!dhcid.empty()) {
+ EXPECT_EQ(dhcid, ncr->getDhcid().toStr());
+ }
+
+ // In some cases, the test doesn't have access to the last transmission
+ // time for the particular client. In such cases, the test can use the
+ // current time as cltt but the it may not check the lease expiration
+ // time for equality but rather check that the lease expiration time
+ // is not greater than the current time + lease lifetime.
+ uint32_t ttl = calculateDdnsTtl(valid_lft);
+ if (not_strict_expire_check) {
+ EXPECT_GE(cltt + ttl, ncr->getLeaseExpiresOn());
+ } else {
+ EXPECT_EQ(cltt + ttl, ncr->getLeaseExpiresOn());
+ }
+
+ EXPECT_EQ(ttl, ncr->getLeaseLength());
+ EXPECT_EQ(isc::dhcp_ddns::ST_NEW, ncr->getStatus());
+ EXPECT_EQ(exp_use_cr, ncr->useConflictResolution());
+
+ // Process the message off the queue
+ ASSERT_NO_THROW(d2_mgr_.runReadyIO());
+ }
+
+
+ /// @brief Tests processing a request with the given client flags
+ ///
+ /// This method creates a request with its FQDN flags set to the given
+ /// value and submits it to the server for processing. It then checks
+ /// the following:
+ /// 1. Did the server generate an ACK with the correct FQDN flags
+ /// 2. If the server should have generated an NCR, did it? and If
+ /// so was it correct?
+ ///
+ /// @param client_flags Mask of client FQDN flags which are true
+ /// @param response_flags Mask of expected FQDN flags in the response
+ void flagVsConfigScenario(const uint8_t client_flags,
+ const uint8_t response_flags) {
+ // Create fake interfaces and open fake sockets.
+ IfaceMgrTestConfig iface_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, client_flags,
+ "myhost.example.com.",
+ Option4ClientFqdn::FULL, true);
+
+ // Process the request.
+ Pkt4Ptr reply;
+ ASSERT_NO_THROW(reply = srv_->processRequest(req));
+
+ // Verify the response and flags.
+ checkResponse(reply, DHCPACK, 1234);
+ checkFqdnFlags(reply, response_flags);
+
+ // NCRs cannot be sent to the d2_mgr unless updates are enabled.
+ if (d2_mgr_.ddnsEnabled()) {
+ // There should be an NCR if response S flag is 1 or N flag is 0.
+ bool exp_fwd = (response_flags & Option4ClientFqdn::FLAG_S);
+ bool exp_rev = (!(response_flags & Option4ClientFqdn::FLAG_N));
+ if (!exp_fwd && !exp_rev) {
+ ASSERT_EQ(0, d2_mgr_.getQueueSize());
+ } else {
+ // Verify that there is one NameChangeRequest as expected.
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD,
+ exp_rev, exp_fwd,
+ reply->getYiaddr().toText(),
+ "myhost.example.com.",
+ "", // empty DHCID means don't check it
+ time(NULL) + subnet_->getValid(),
+ subnet_->getValid(), true);
+ }
+ }
+ }
+};
+
+// Tests the following scenario:
+// - Updates are enabled
+// - All overrides are off
+// - Client requests forward update (N = 0, S = 1)
+//
+// Server should perform the update:
+// - Response flags should N = 0, S = 1, O = 0
+// - Should queue an NCR
+TEST_F(NameDhcpv4SrvTest, updatesEnabled) {
+ flagVsConfigScenario((Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_S),
+ (Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_S));
+}
+
+// Tests the following scenario
+// - Updates are disabled
+// - Client requests forward update (N = 0, S = 1)
+//
+// Server should NOT perform updates:
+// - Response flags should N = 1, S = 0, O = 1
+// - Should not queue any NCRs
+TEST_F(NameDhcpv4SrvTest, updatesDisabled) {
+ disableD2();
+ flagVsConfigScenario((Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_S),
+ (Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_N |
+ Option4ClientFqdn::FLAG_O));
+}
+
+// Tests the following scenario:
+// - Updates are enabled
+// - All overrides are off.
+// - Client requests no updates (N = 1, S = 0)
+//
+// Server should NOT perform updates:
+// - Response flags should N = 1, S = 0, O = 0
+// - Should not queue any NCRs
+TEST_F(NameDhcpv4SrvTest, respectNoUpdate) {
+ flagVsConfigScenario((Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_N),
+ (Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_N));
+}
+
+// Tests the following scenario:
+// - Updates are enabled
+// - override-no-update is on
+// - Client requests no updates (N = 1, S = 0)
+//
+// Server should override "no update" request and perform updates:
+// - Response flags should be N = 0, S = 1, O = 1
+// - Should queue an NCR
+TEST_F(NameDhcpv4SrvTest, overrideNoUpdate) {
+ enableD2(OVERRIDE_NO_UPDATE);
+ flagVsConfigScenario((Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_N),
+ (Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_O));
+}
+
+// Tests the following scenario:
+// - Updates are enabled
+// - All overrides are off.
+// - Client requests delegation (N = 0, S = 0)
+//
+// Server should respect client's delegation request and NOT do updates:
+
+// - Response flags should be N = 0, S = 0, O = 0
+// - Should not queue any NCRs
+TEST_F(NameDhcpv4SrvTest, respectClientDelegation) {
+
+ flagVsConfigScenario(Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::FLAG_E);
+}
+
+// Tests the following scenario:
+// - Updates are enabled
+// - override-client-update is on.
+// - Client requests delegation (N = 0, S = 0)
+//
+// Server should override client's delegation request and do updates:
+// - Response flags should be N = 0, S = 1, O = 1
+// - Should queue an NCR
+TEST_F(NameDhcpv4SrvTest, overrideClientDelegation) {
+ // Turn on override-client-update.
+ enableD2(OVERRIDE_CLIENT_UPDATE);
+
+ flagVsConfigScenario(Option4ClientFqdn::FLAG_E,
+ (Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_O));
+}
+
+// Test that server processes the Hostname option sent by a client and
+// responds with the Hostname option to confirm that the server has
+// taken responsibility for the update.
+TEST_F(NameDhcpv4SrvTest, serverUpdateHostname) {
+ Pkt4Ptr query;
+ ASSERT_NO_THROW(query = generatePktWithHostname(DHCPREQUEST,
+ "myhost.example.com."));
+ OptionStringPtr hostname;
+ ASSERT_NO_THROW(hostname = processHostname(query));
+
+ ASSERT_TRUE(hostname);
+ EXPECT_EQ("myhost.example.com.", hostname->getValue());
+
+}
+
+// Test that the server skips processing of a mal-formed Hostname options.
+// - First scenario the hostname has an empty label
+// - Second scenario the hostname option causes an internal parsing error
+// in dns::Name(). The content was created by fuzz testing.
+TEST_F(NameDhcpv4SrvTest, serverUpdateMalformedHostname) {
+ Pkt4Ptr query;
+
+ // Hostname should not be able to have an emtpy label.
+ ASSERT_NO_THROW(query = generatePktWithHostname(DHCPREQUEST,
+ "abc..example.com"));
+ OptionStringPtr hostname;
+ ASSERT_NO_THROW(hostname = processHostname(query));
+ EXPECT_FALSE(hostname);
+
+ // The following vector matches malformed hostname data produced by
+ // fuzz testing that causes an internal error in dns::Name parsing logic.
+ std::vector<uint8_t> badname {
+ 0xff,0xff,0x7f,0x00,0x00,0x00,0x7f,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x04,0x63,0x82,0x53,0x63,0x35,0x01,0x01,0x3d,0x07,0x01,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x19,0x0c,0x4e,0x01,0x00,0x07,0x08,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x35,0x01,0x05,0x3a,0x04,0x00,0x00,0x07,0x08,0x3b,0x04,0x00,
+ 0x00,0x2e,0x3b,0x04,0x00,0x19,0x2e,0x00,0x00,0x00,0x0a,0x00,0x12,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x0b,0x82,0x01,0xfc,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x35,0x01,0x05,0x3a,0x04,0x00,0x00,0x07,0x08,0x3b,0x04,
+ 0x00,0x00,0x2e,0x3b,0x04,0x00,0x19,0x2e,0x56,0x00,0x00,0x0a,0x00,0x12,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x0b,0x82,0x01,0xfc,0x42,0x00,0x00,0x00,0x00,0x19,0x0c,
+ 0x4e,0x01,0x05,0x3a,0x04,0xde,0x00,0x07,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x35,0x01,0x05,0x3a,0x07,0x08,0x3b,0x04,0x00,0x00,0x2e,0x3b,0x04,
+ 0x00,0x19,0x2e,0x56,0x40,0x00,0x00,0x00,0x00,0x00,0x0a,0x00,0x12,0x00,0x00,0x00,
+ 0x00,0x00,0x19,0x00,0x0b,0x82,0x01,0xfc,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x35,0x01,0x05,0xff,0xff,0x05,0x00,0x07,0x08,0x3b,0x04,
+ 0x00,0x00,0x2e,0x3b
+ };
+
+ std::string badnamestr(badname.begin(), badname.end());
+ ASSERT_NO_THROW(query = generatePktWithHostname(DHCPREQUEST,
+ badnamestr));
+ ASSERT_NO_THROW(hostname = processHostname(query));
+ EXPECT_FALSE(hostname);
+}
+
+// Test that the server does not see an empty Hostname option.
+// Suppressing the empty Hostname is done in libdhcp++ during
+// unpackcing, so technically we don't need this test but,
+// hey it's already written.
+TEST_F(NameDhcpv4SrvTest, serverUpdateEmptyHostname) {
+ Pkt4Ptr query;
+ ASSERT_NO_THROW(query = generatePktWithEmptyHostname(DHCPREQUEST));
+ OptionStringPtr hostname;
+ ASSERT_NO_THROW(hostname = processHostname(query));
+ EXPECT_FALSE(hostname);
+}
+
+// Test that server generates the fully qualified domain name for the client
+// if client supplies the partial name.
+TEST_F(NameDhcpv4SrvTest, serverUpdateForwardPartialNameFqdn) {
+ Pkt4Ptr query = generatePktWithFqdn(DHCPREQUEST,
+ Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_S,
+ "myhost",
+ Option4ClientFqdn::PARTIAL,
+ true);
+
+ testProcessFqdn(query,
+ Option4ClientFqdn::FLAG_E | Option4ClientFqdn::FLAG_S,
+ "myhost.example.com.");
+
+}
+
+// Test that server generates the fully qualified domain name for the client
+// if client supplies the unqualified name in the Hostname option.
+TEST_F(NameDhcpv4SrvTest, serverUpdateUnqualifiedHostname) {
+ Pkt4Ptr query;
+ ASSERT_NO_THROW(query = generatePktWithHostname(DHCPREQUEST, "myhost"));
+ OptionStringPtr hostname;
+ ASSERT_NO_THROW(hostname = processHostname(query));
+
+ ASSERT_TRUE(hostname);
+ EXPECT_EQ("myhost.example.com", hostname->getValue());
+
+}
+
+// Test that server sets empty domain-name in the FQDN option when client
+// supplied no domain-name. The domain-name is supposed to be set after the
+// lease is acquired. The domain-name is then generated from the IP address
+// assigned to a client.
+TEST_F(NameDhcpv4SrvTest, serverUpdateForwardNoNameFqdn) {
+ Pkt4Ptr query = generatePktWithFqdn(DHCPREQUEST,
+ Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_S,
+ "",
+ Option4ClientFqdn::PARTIAL,
+ true);
+
+ testProcessFqdn(query,
+ Option4ClientFqdn::FLAG_E | Option4ClientFqdn::FLAG_S,
+ "", Option4ClientFqdn::PARTIAL);
+
+}
+
+// Test that exactly one NameChangeRequest is generated when the new lease
+// has been acquired (old lease is NULL).
+TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsNewLease) {
+ Lease4Ptr lease = createLease(IOAddress("192.0.2.3"), "myhost.example.com.",
+ true, true);
+ Lease4Ptr old_lease;
+
+ ASSERT_TRUE(getDdnsParams()->getEnableUpdates());
+
+ ASSERT_NO_THROW(srv_->createNameChangeRequests(lease, old_lease, *getDdnsParams()));
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ "192.0.2.3", "myhost.example.com.",
+ "00010132E91AA355CFBB753C0F0497A5A940436965"
+ "B68B6D438D98E680BF10B09F3BCF",
+ lease->cltt_, 100);
+}
+
+// Verify that conflict resolution is disabled in the NCR when it is
+// disabled for the lease's subnet.
+TEST_F(NameDhcpv4SrvTest, noConflictResolution) {
+ Lease4Ptr lease = createLease(IOAddress("192.0.2.3"), "myhost.example.com.",
+ true, true);
+ Lease4Ptr old_lease;
+
+ ASSERT_TRUE(getDdnsParams()->getEnableUpdates());
+ subnet_->setDdnsUseConflictResolution(false);
+
+ ASSERT_NO_THROW(srv_->createNameChangeRequests(lease, old_lease, *getDdnsParams()));
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ "192.0.2.3", "myhost.example.com.",
+ "00010132E91AA355CFBB753C0F0497A5A940436965"
+ "B68B6D438D98E680BF10B09F3BCF",
+ lease->cltt_, 100, false, false);
+}
+
+
+// Verifies that createNameChange request only generates requests
+// if the situation dictates that it should. It checks:
+//
+// -# enable-updates true or false
+// -# update-on-renew true or false
+// -# Whether or not there is an old lease
+// -# Whether or not the FQDN has changed between old and new lease
+TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsUpdateOnRenew) {
+
+ Lease4Ptr lease1 = createLease(IOAddress("192.0.2.3"), "one.example.com.",
+ true, true);
+ // Comparison should be case insensitive, so turning some of the
+ // characters of the old lease hostname to upper case should not
+ // trigger NCRs.
+ Lease4Ptr lease2 = createLease(IOAddress("192.0.2.3"),
+ "two.example.com.", true, true);
+ struct Scenario {
+ std::string description_;
+ bool send_updates_;
+ bool update_on_renew_;
+ Lease4Ptr old_lease_;
+ Lease4Ptr new_lease_;
+ 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 old lease",
+ send_updates, !update_on_renew, Lease4Ptr(), lease1, !remove, add
+ },
+ {
+ "#2 update-on-renew false, no change in fqdn",
+ send_updates, !update_on_renew, lease1, lease1, !remove, !add
+ },
+ {
+ "#3 update-on-renew is false, change in fqdn",
+ send_updates, !update_on_renew, lease1, lease2, remove, add
+ },
+ {
+ "#4 update-on-renew is true, no old lease",
+ send_updates, update_on_renew, Lease4Ptr(), lease1, !remove, add
+ },
+ {
+ "#5 update-on-renew is true, no change in fqdn",
+ send_updates, update_on_renew, lease1, lease1, remove, add
+ },
+ {
+ "#6 update-on-renew is true, change in fqdn",
+ send_updates, update_on_renew, lease1, lease2, remove, add
+ },
+ // All prior scenarios test with send-updates true. We really
+ // only need one with it false.
+ {
+ "#7 send-updates false, update-on-renew is true, change in fqdn",
+ !send_updates, update_on_renew, lease1, lease2, !remove, !add
+ }
+ };
+
+ // Iterate over test scenarios.
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_); {
+ // 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());
+
+ // Call createNameChangeRequests()
+ ASSERT_NO_THROW(srv_->createNameChangeRequests(scenario.new_lease_,
+ scenario.old_lease_,
+ *getDdnsParams()));
+ // 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,
+ scenario.old_lease_->addr_.toText(),
+ scenario.old_lease_->hostname_, "", time(NULL),
+ scenario.old_lease_->valid_lft_, true);
+ }
+
+ // If we expect an add, check it.
+ if (scenario.add_ > 0) {
+ // Verify NCR content
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ scenario.new_lease_->addr_.toText(),
+ scenario.new_lease_->hostname_, "", time(NULL),
+ scenario.new_lease_->valid_lft_, true);
+ }
+ }
+ }
+}
+
+// Test that the OFFER message generated as a result of the DISCOVER message
+// processing will not result in generation of the NameChangeRequests.
+TEST_F(NameDhcpv4SrvTest, processDiscover) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ Pkt4Ptr req = generatePktWithFqdn(DHCPDISCOVER, Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ "myhost.example.com.",
+ Option4ClientFqdn::FULL, true);
+
+ Pkt4Ptr reply;
+ ASSERT_NO_THROW(reply = srv_->processDiscover(req));
+ checkResponse(reply, DHCPOFFER, 1234);
+
+ EXPECT_EQ(0, d2_mgr_.getQueueSize());
+}
+
+// Test that server generates client's hostname from the IP address assigned
+// to it when DHCPv4 Client FQDN option specifies an empty domain-name.
+TEST_F(NameDhcpv4SrvTest, processRequestFqdnEmptyDomainName) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ "", Option4ClientFqdn::PARTIAL, true);
+
+ Pkt4Ptr reply;
+ ASSERT_NO_THROW(reply = srv_->processRequest(req));
+
+ checkResponse(reply, DHCPACK, 1234);
+
+ // Verify that there is one NameChangeRequest generated.
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+
+ // The hostname is generated from the IP address acquired (yiaddr).
+ std::string hostname = generatedNameFromAddress(reply->getYiaddr());
+
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ reply->getYiaddr().toText(), hostname,
+ "", // empty DHCID forces that it is not checked
+ time(NULL) + subnet_->getValid(),
+ subnet_->getValid(), true);
+
+ req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ "", Option4ClientFqdn::PARTIAL, true);
+
+ ASSERT_NO_THROW(reply = srv_->processRequest(req));
+
+ checkResponse(reply, DHCPACK, 1234);
+
+ // Verify that there are no NameChangeRequests generated.
+ ASSERT_EQ(0, d2_mgr_.getQueueSize());
+}
+
+// Test that server generates client's hostname from the IP address assigned
+// to it when DHCPv4 Client FQDN option specifies an empty domain-name AND
+// ddns updates are disabled.
+TEST_F(NameDhcpv4SrvTest, processRequestEmptyDomainNameDisabled) {
+ // Create fake interfaces and open fake sockets.
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ disableD2();
+ Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ "", Option4ClientFqdn::PARTIAL, true);
+ Pkt4Ptr reply;
+ ASSERT_NO_THROW(reply = srv_->processRequest(req));
+
+ checkResponse(reply, DHCPACK, 1234);
+
+ Option4ClientFqdnPtr fqdn = getClientFqdnOption(reply);
+ ASSERT_TRUE(fqdn);
+
+ // The hostname is generated from the IP address acquired (yiaddr).
+ std::string hostname = generatedNameFromAddress(reply->getYiaddr());
+
+ EXPECT_EQ(hostname, fqdn->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, fqdn->getDomainNameType());
+}
+
+
+// Test that server generates client's hostname from the IP address assigned
+// to it when Hostname option carries the top level domain-name.
+TEST_F(NameDhcpv4SrvTest, processRequestTopLevelHostname) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ Pkt4Ptr req = generatePktWithHostname(DHCPREQUEST, ".");
+ // Set interface for the incoming packet. The server requires it to
+ // generate client id.
+ req->setIface("eth1");
+ req->setIndex(ETH1_INDEX);
+
+ Pkt4Ptr reply;
+ ASSERT_NO_THROW(reply = srv_->processRequest(req));
+
+ checkResponse(reply, DHCPACK, 1234);
+
+ // Verify that there is one NameChangeRequest generated.
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+
+ // The hostname is generated from the IP address acquired (yiaddr).
+ std::string hostname = generatedNameFromAddress(reply->getYiaddr());
+
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ reply->getYiaddr().toText(), hostname,
+ "", // empty DHCID forces that it is not checked
+ time(NULL), subnet_->getValid(), true);
+}
+
+// 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(NameDhcpv4SrvTest, processTwoRequestsFqdn) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ Pkt4Ptr req1 = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ "myhost.example.com.",
+ Option4ClientFqdn::FULL, true);
+
+ Pkt4Ptr reply;
+ ASSERT_NO_THROW(reply = srv_->processRequest(req1));
+
+ checkResponse(reply, DHCPACK, 1234);
+
+ // Verify that there is one NameChangeRequest generated.
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ reply->getYiaddr().toText(), "myhost.example.com.",
+ "00010132E91AA355CFBB753C0F0497A5A940436"
+ "965B68B6D438D98E680BF10B09F3BCF",
+ time(NULL), subnet_->getValid(), true);
+
+ // Create another Request message but with a different FQDN. Server
+ // should generate two NameChangeRequests: one to remove existing entry,
+ // another one to add new entry with updated domain-name.
+ Pkt4Ptr req2 = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ "otherhost.example.com.",
+ Option4ClientFqdn::FULL, true);
+
+ ASSERT_NO_THROW(reply = srv_->processRequest(req2));
+
+ checkResponse(reply, DHCPACK, 1234);
+
+ // There should be two NameChangeRequests. Verify that they are valid.
+ ASSERT_EQ(2, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+ reply->getYiaddr().toText(),
+ "myhost.example.com.",
+ "00010132E91AA355CFBB753C0F0497A5A940436"
+ "965B68B6D438D98E680BF10B09F3BCF",
+ time(NULL), subnet_->getValid(), true);
+
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ reply->getYiaddr().toText(),
+ "otherhost.example.com.",
+ "000101A5AEEA7498BD5AD9D3BF600E49FF39A7E3"
+ "AFDCE8C3D0E53F35CC584DD63C89CA",
+ time(NULL), subnet_->getValid(), true);
+}
+
+// Test that client may send two requests, each carrying Hostname option with
+// a different name. Server should use existing lease for the second request
+// but modify the DNS entries for the lease according to the contents of the
+// Hostname sent in the second request.
+TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Case in a hostname should be ignored.
+ Pkt4Ptr req1 = generatePktWithHostname(DHCPREQUEST, "Myhost.Example.Com.");
+
+ // Set interface for the incoming packet. The server requires it to
+ // generate client id.
+ req1->setIface("eth1");
+ req1->setIndex(ETH1_INDEX);
+
+ Pkt4Ptr reply;
+ ASSERT_NO_THROW(reply = srv_->processRequest(req1));
+
+ checkResponse(reply, DHCPACK, 1234);
+
+ // Verify that there is one NameChangeRequest generated.
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ reply->getYiaddr().toText(), "myhost.example.com.",
+ "00010132E91AA355CFBB753C0F0497A5A940436"
+ "965B68B6D438D98E680BF10B09F3BCF",
+ time(NULL), subnet_->getValid(), true);
+
+ // Create another Request message but with a different Hostname. Server
+ // should generate two NameChangeRequests: one to remove existing entry,
+ // another one to add new entry with updated domain-name.
+ Pkt4Ptr req2 = generatePktWithHostname(DHCPREQUEST, "otherhost");
+
+ // Set interface for the incoming packet. The server requires it to
+ // generate client id.
+ req2->setIface("eth1");
+ req2->setIndex(ETH1_INDEX);
+
+ ASSERT_NO_THROW(reply = srv_->processRequest(req2));
+
+ checkResponse(reply, DHCPACK, 1234);
+
+ // There should be two NameChangeRequests. Verify that they are valid.
+ ASSERT_EQ(2, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+ reply->getYiaddr().toText(),
+ "myhost.example.com.",
+ "00010132E91AA355CFBB753C0F0497A5A940436"
+ "965B68B6D438D98E680BF10B09F3BCF",
+ time(NULL), subnet_->getValid(), true);
+
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ reply->getYiaddr().toText(),
+ "otherhost.example.com.",
+ "000101A5AEEA7498BD5AD9D3BF600E49FF39A7E3"
+ "AFDCE8C3D0E53F35CC584DD63C89CA",
+ time(NULL), subnet_->getValid(), true);
+}
+
+// Test that client may send two requests, each carrying the same FQDN option.
+// Server should renew existing lease for the second request without generating
+// any NCRs.
+TEST_F(NameDhcpv4SrvTest, processRequestRenewFqdn) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ Pkt4Ptr req1 = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ "myhost.example.com.",
+ Option4ClientFqdn::FULL, true);
+
+ Pkt4Ptr reply;
+ ASSERT_NO_THROW(reply = srv_->processRequest(req1));
+
+ checkResponse(reply, DHCPACK, 1234);
+
+ // Verify that there is one NameChangeRequest generated.
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ reply->getYiaddr().toText(), "myhost.example.com.",
+ "00010132E91AA355CFBB753C0F0497A5A940436"
+ "965B68B6D438D98E680BF10B09F3BCF",
+ time(NULL), subnet_->getValid(), true);
+
+ // Create another Request message with the same FQDN. Case changes in the
+ // hostname should be ignored. Server should generate no NameChangeRequests.
+ Pkt4Ptr req2 = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ "Myhost.Example.Com.",
+ Option4ClientFqdn::FULL, true);
+
+ ASSERT_NO_THROW(reply = srv_->processRequest(req2));
+
+ checkResponse(reply, DHCPACK, 1234);
+
+ // There should be no NameChangeRequests.
+ ASSERT_EQ(0, d2_mgr_.getQueueSize());
+}
+
+// Test that client may send two requests, each carrying the same hostname
+// option. Server should renew existing lease for the second request without
+// generating any NCRs.
+TEST_F(NameDhcpv4SrvTest, processRequestRenewHostname) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ Pkt4Ptr req1 = generatePktWithHostname(DHCPREQUEST, "myhost.example.com.");
+
+ // Set interface for the incoming packet. The server requires it to
+ // generate client id.
+ req1->setIface("eth1");
+ req1->setIndex(ETH1_INDEX);
+
+ Pkt4Ptr reply;
+ ASSERT_NO_THROW(reply = srv_->processRequest(req1));
+
+ checkResponse(reply, DHCPACK, 1234);
+
+ // Verify that there is one NameChangeRequest generated.
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ reply->getYiaddr().toText(), "myhost.example.com.",
+ "00010132E91AA355CFBB753C0F0497A5A940436"
+ "965B68B6D438D98E680BF10B09F3BCF",
+ time(NULL), subnet_->getValid(), true);
+
+ // Create another Request message with the same Hostname. Case changes in the
+ // hostname should be ignored. Server should generate no NameChangeRequests.
+ Pkt4Ptr req2 = generatePktWithHostname(DHCPREQUEST, "Myhost.Example.Com.");
+
+ // Set interface for the incoming packet. The server requires it to
+ // generate client id.
+ req2->setIface("eth1");
+ req2->setIndex(ETH1_INDEX);
+
+ ASSERT_NO_THROW(reply = srv_->processRequest(req2));
+
+ checkResponse(reply, DHCPACK, 1234);
+
+ // There should be no NameChangeRequests.
+ ASSERT_EQ(0, d2_mgr_.getQueueSize());
+}
+
+// Test that when a release message is sent for a previously acquired lease,
+// DDNS updates are enabled that the server generates a NameChangeRequest
+// to remove entries corresponding to the released lease.
+TEST_F(NameDhcpv4SrvTest, processRequestRelease) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Verify the updates are enabled.
+ ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+
+ // Create and process a lease request so we have a lease to release.
+ Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ "myhost.example.com.",
+ Option4ClientFqdn::FULL, true);
+ Pkt4Ptr reply;
+ ASSERT_NO_THROW(reply = srv_->processRequest(req));
+ checkResponse(reply, DHCPACK, 1234);
+
+ // Verify that there is one NameChangeRequest generated for lease.
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ reply->getYiaddr().toText(), "myhost.example.com.",
+ "00010132E91AA355CFBB753C0F0497A5A940436"
+ "965B68B6D438D98E680BF10B09F3BCF",
+ time(NULL), subnet_->getValid(), true);
+
+ // Create and process the Release message.
+ Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
+ rel->setCiaddr(reply->getYiaddr());
+ rel->setRemoteAddr(IOAddress("192.0.2.3"));
+ rel->addOption(generateClientId());
+ rel->addOption(srv_->getServerID());
+ ASSERT_NO_THROW(srv_->processRelease(rel));
+
+ // The lease has been removed, so there should be a NameChangeRequest to
+ // remove corresponding DNS entries.
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+ reply->getYiaddr().toText(), "myhost.example.com.",
+ "00010132E91AA355CFBB753C0F0497A5A940436"
+ "965B68B6D438D98E680BF10B09F3BCF",
+ time(NULL), subnet_->getValid(), true);
+}
+
+// Test that when the Release message is sent for a previously acquired lease
+// and DDNS updates are disabled that server does NOT generate a
+// NameChangeRequest to remove entries corresponding to the released lease.
+// Queue size is not available when updates are not enabled, however,
+// attempting to send a NCR when updates disabled will result in a throw.
+// If no throws are experienced then no attempt was made to send a NCR.
+TEST_F(NameDhcpv4SrvTest, processRequestReleaseUpdatesDisabled) {
+ // Create fake interfaces and open fake sockets.
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Disable DDNS.
+ disableD2();
+ ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+
+ // Create and process a lease request so we have a lease to release.
+ Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ "myhost.example.com.",
+ Option4ClientFqdn::FULL, true);
+ Pkt4Ptr reply;
+ ASSERT_NO_THROW(reply = srv_->processRequest(req));
+ checkResponse(reply, DHCPACK, 1234);
+
+ // Create and process the Release message.
+ Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
+ rel->setCiaddr(reply->getYiaddr());
+ rel->setRemoteAddr(IOAddress("192.0.2.3"));
+ rel->addOption(generateClientId());
+ rel->addOption(srv_->getServerID());
+ ASSERT_NO_THROW(srv_->processRelease(rel));
+}
+
+// This test verifies that the server sends the FQDN option to the client
+// with the reserved hostname.
+TEST_F(NameDhcpv4SrvTest, fqdnReservation) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Use HW address that matches the reservation entry in the configuration.
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+ // Configure DHCP server.
+ configure(CONFIGS[0], *client.getServer());
+ // Make sure that DDNS is enabled.
+ ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+ ASSERT_NO_THROW(client.getServer()->startD2());
+ // Include the Client FQDN option.
+ ASSERT_NO_THROW(client.includeFQDN(Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E,
+ "client-name", Option4ClientFqdn::PARTIAL));
+ // Send the DHCPDISCOVER.
+ ASSERT_NO_THROW(client.doDiscover());
+
+ // Make sure that the server responded.
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+
+ // Obtain the FQDN option sent in the response and make sure that the server
+ // has used the hostname reserved for this client.
+ Option4ClientFqdnPtr fqdn;
+ fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
+ ASSERT_TRUE(fqdn);
+ EXPECT_EQ("unique-host.example.com.", fqdn->getDomainName());
+
+ // When receiving DHCPDISCOVER, no NCRs should be generated.
+ EXPECT_EQ(0, d2_mgr_.getQueueSize());
+
+ // Now send the DHCPREQUEST with including the FQDN option.
+ ASSERT_NO_THROW(client.doRequest());
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Once again check that the FQDN is as expected.
+ fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
+ ASSERT_TRUE(fqdn);
+ EXPECT_EQ("unique-host.example.com.", fqdn->getDomainName());
+
+ {
+ SCOPED_TRACE("Verify the correctness of the NCR for the"
+ "unique-host.example.com");
+
+ // Because this is a new lease, there should be one NCR which adds the
+ // new DNS entry.
+ ASSERT_EQ(1, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ resp->getYiaddr().toText(),
+ "unique-host.example.com.",
+ "000001B6547DCC62E44C4D1A42D0A05B149EA1168"
+ "01A9481A98E3A876A9E0D261F8326",
+ time(NULL), subnet_->getValid(), true);
+ }
+
+ // And that this FQDN has been stored in the lease database.
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(client.config_.lease_.addr_);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("unique-host.example.com.", lease->hostname_);
+
+ // Reconfigure DHCP server to use a different hostname for the client.
+ configure(CONFIGS[1], *client.getServer());
+ // Make sure that DDNS is enabled.
+ ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+ ASSERT_NO_THROW(client.getServer()->startD2());
+
+ // Client is in the renewing state.
+ client.setState(Dhcp4Client::RENEWING);
+ client.doRequest();
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // The new FQDN should contain a different name this time.
+ fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
+ ASSERT_TRUE(fqdn);
+ EXPECT_EQ("foobar.fake-suffix.isc.org.", fqdn->getDomainName());
+
+ // And the lease in the lease database should also contain this new FQDN.
+ lease = LeaseMgrFactory::instance().getLease4(client.config_.lease_.addr_);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("foobar.fake-suffix.isc.org.", lease->hostname_);
+
+ // Now there should be two name NCRs. One that removes the previous entry
+ // and the one that adds a new entry for the new hostname.
+ ASSERT_EQ(2, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+
+ {
+ SCOPED_TRACE("Verify the correctness of the CHG_REMOVE NCR for the "
+ "unique-host.example.com");
+
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+ resp->getYiaddr().toText(),
+ "unique-host.example.com.",
+ "000001B6547DCC62E44C4D1A42D0A05B149EA1168"
+ "01A9481A98E3A876A9E0D261F8326",
+ time(NULL), subnet_->getValid(), true);
+ }
+
+ {
+ SCOPED_TRACE("Verify the correctness of the CHG_ADD NCR for the "
+ "foobar.fake-suffix.isc.org");
+
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ resp->getYiaddr().toText(),
+ "foobar.fake-suffix.isc.org.",
+ "0000017C29B3C236344924E448E247F3FD56C7E9"
+ "167B3397B1305FB664C160B967CE1F",
+ time(NULL), subnet_->getValid(), true);
+ }
+}
+
+// This test verifies that the server sends the Hostname option to the client
+// with the reserved hostname.
+TEST_F(NameDhcpv4SrvTest, hostnameReservation) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Use HW address that matches the reservation entry in the configuration.
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+ // Configure DHCP server.
+ configure(CONFIGS[0], *client.getServer());
+ // Make sure that DDNS is enabled.
+ ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+ ASSERT_NO_THROW(client.getServer()->startD2());
+ // Include the Hostname option.
+ ASSERT_NO_THROW(client.includeHostname("client-name"));
+
+ // Send the DHCPDISCOVER
+ ASSERT_NO_THROW(client.doDiscover());
+
+ // Make sure that the server responded.
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+
+ // Obtain the Hostname option sent in the response and make sure that the server
+ // has used the hostname reserved for this client.
+ OptionStringPtr hostname;
+ hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
+ ASSERT_TRUE(hostname);
+ EXPECT_EQ("unique-host.example.com", hostname->getValue());
+
+ // Now send the DHCPREQUEST with including the Hostname option.
+ ASSERT_NO_THROW(client.doRequest());
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Once again check that the Hostname is as expected.
+ hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
+ ASSERT_TRUE(hostname);
+ EXPECT_EQ("unique-host.example.com", hostname->getValue());
+
+ // And that this hostname has been stored in the lease database.
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(client.config_.lease_.addr_);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("unique-host.example.com", lease->hostname_);
+
+ // Because this is a new lease, there should be one NCR which adds the
+ // new DNS entry.
+ ASSERT_EQ(1, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+ {
+ SCOPED_TRACE("Verify the correctness of the NCR for the"
+ "unique-host.example.com");
+
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ resp->getYiaddr().toText(),
+ "unique-host.example.com.",
+ "000001B6547DCC62E44C4D1A42D0A05B149EA1168"
+ "01A9481A98E3A876A9E0D261F8326",
+ time(NULL), subnet_->getValid(), true);
+ }
+
+ // Reconfigure DHCP server to use a different hostname for the client.
+ configure(CONFIGS[1], *client.getServer());
+ // Make sure that DDNS is enabled.
+ ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+ ASSERT_NO_THROW(client.getServer()->startD2());
+
+ // Client is in the renewing state.
+ client.setState(Dhcp4Client::RENEWING);
+ client.doRequest();
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // The new hostname should be different than previously.
+ hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
+ ASSERT_TRUE(hostname);
+ EXPECT_EQ("foobar.fake-suffix.isc.org", hostname->getValue());
+
+ // And the lease in the lease database should also contain this new FQDN.
+ lease = LeaseMgrFactory::instance().getLease4(client.config_.lease_.addr_);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("foobar.fake-suffix.isc.org", lease->hostname_);
+
+ // Now there should be two name NCRs. One that removes the previous entry
+ // and the one that adds a new entry for the new hostname.
+ ASSERT_EQ(2, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+ {
+ SCOPED_TRACE("Verify the correctness of the CHG_REMOVE NCR for the "
+ "unique-host.example.com");
+
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+ resp->getYiaddr().toText(),
+ "unique-host.example.com.",
+ "000001B6547DCC62E44C4D1A42D0A05B149EA1168"
+ "01A9481A98E3A876A9E0D261F8326",
+ time(NULL), subnet_->getValid(), true);
+ }
+
+ {
+ SCOPED_TRACE("Verify the correctness of the CHG_ADD NCR for the "
+ "foobar.fake-suffix.isc.org");
+
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ resp->getYiaddr().toText(),
+ "foobar.fake-suffix.isc.org.",
+ "0000017C29B3C236344924E448E247F3FD56C7E9"
+ "167B3397B1305FB664C160B967CE1F",
+ time(NULL), subnet_->getValid(), true);
+ }
+}
+
+// This test verifies that the server sends the Hostname option to the client
+// with hostname reservation and which included hostname option code in the
+// Parameter Request List.
+TEST_F(NameDhcpv4SrvTest, hostnameReservationPRL) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Use HW address that matches the reservation entry in the configuration.
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+ // Configure DHCP server.
+ configure(CONFIGS[4], *client.getServer());
+ // Make sure that DDNS is enabled.
+ ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+ // Request Hostname option.
+ ASSERT_NO_THROW(client.requestOption(DHO_HOST_NAME));
+
+ // Send the DHCPDISCOVER
+ ASSERT_NO_THROW(client.doDiscover());
+
+ // Make sure that the server responded.
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+
+ // Obtain the Hostname option sent in the response and make sure that the server
+ // has used the hostname reserved for this client.
+ OptionStringPtr hostname;
+ hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
+ ASSERT_TRUE(hostname);
+ EXPECT_EQ("reserved.example.com", hostname->getValue());
+
+ // Now send the DHCPREQUEST with including the Hostname option.
+ ASSERT_NO_THROW(client.doRequest());
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Once again check that the Hostname is as expected.
+ hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
+ ASSERT_TRUE(hostname);
+ EXPECT_EQ("reserved.example.com", hostname->getValue());
+}
+
+// This test verifies that the server sends the Hostname option to the client
+// with partial hostname reservation and with the global qualifying-suffix set.
+TEST_F(NameDhcpv4SrvTest, hostnameReservationNoDNSQualifyingSuffix) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Use HW address that matches the reservation entry in the configuration.
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+ // Configure DHCP server.
+ configure(CONFIGS[5], *client.getServer());
+ // Make sure that DDNS is enabled.
+ ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+ // Include the Hostname option.
+ ASSERT_NO_THROW(client.includeHostname("client-name"));
+
+ // Send the DHCPDISCOVER
+ ASSERT_NO_THROW(client.doDiscover());
+
+ // Make sure that the server responded.
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+
+ // Obtain the Hostname option sent in the response and make sure that the server
+ // has used the hostname reserved for this client.
+ OptionStringPtr hostname;
+ hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
+ ASSERT_TRUE(hostname);
+ EXPECT_EQ("foo-bar.example.isc.org", hostname->getValue());
+
+ // Now send the DHCPREQUEST with including the Hostname option.
+ ASSERT_NO_THROW(client.doRequest());
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Once again check that the Hostname is as expected.
+ hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
+ ASSERT_TRUE(hostname);
+ EXPECT_EQ("foo-bar.example.isc.org", hostname->getValue());
+}
+
+// Test verifies that the server properly generates a FQDN when the client
+// FQDN name is blank, whether or not DDNS updates are enabled. It also
+// verifies that the lease is only in the database following a DHCPREQUEST and
+// that the lease contains the generated FQDN.
+TEST_F(NameDhcpv4SrvTest, emptyFqdn) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ isc::asiolink::IOAddress expected_address("10.0.0.10");
+ std::string expected_fqdn("myhost-10-0-0-10.fake-suffix.isc.org.");
+
+ // Load a configuration with DDNS updates disabled
+ configure(CONFIGS[2], *client.getServer());
+ ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+
+ // Include the Client FQDN option.
+ ASSERT_NO_THROW(client.includeFQDN((Option4ClientFqdn::FLAG_S
+ | Option4ClientFqdn::FLAG_E),
+ "", Option4ClientFqdn::PARTIAL));
+
+ // Send the DHCPDISCOVER
+ ASSERT_NO_THROW(client.doDiscover());
+
+ // Make sure that the server responded.
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+
+ // Make sure the response FQDN has the generated name and FQDN flags are
+ // correct for updated disabled.
+ Option4ClientFqdnPtr fqdn;
+ fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
+ ASSERT_TRUE(fqdn);
+ EXPECT_EQ(expected_fqdn, fqdn->getDomainName());
+ checkFqdnFlags(resp, (Option4ClientFqdn::FLAG_N |
+ Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_O));
+
+ // Make sure the lease is NOT in the database.
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(IOAddress(expected_address));
+ ASSERT_FALSE(lease);
+
+ // Now test with updates enabled
+ configure(CONFIGS[3], *client.getServer());
+ ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+ ASSERT_NO_THROW(client.getServer()->startD2());
+
+ // Send the DHCPDISCOVER
+ ASSERT_NO_THROW(client.doDiscover());
+
+ // Make sure that the server responded.
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+
+ // Make sure the response FQDN has the generated name and FQDN flags are
+ // correct for updates enabled.
+ fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
+ ASSERT_TRUE(fqdn);
+ EXPECT_EQ(expected_fqdn, fqdn->getDomainName());
+ checkFqdnFlags(resp, (Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_S));
+
+ // Make sure the lease is NOT in the database.
+ lease = LeaseMgrFactory::instance().getLease4(IOAddress(expected_address));
+ ASSERT_FALSE(lease);
+
+ // Do a DORA and verify that the lease exists and the name is correct.
+ ASSERT_NO_THROW(client.doDORA());
+
+ // Make sure that the server responded.
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Make sure the response FQDN has the generated name and FQDN flags are
+ // correct for updates enabled.
+ fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
+ ASSERT_TRUE(fqdn);
+ EXPECT_EQ(expected_fqdn, fqdn->getDomainName());
+ checkFqdnFlags(resp, (Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_S));
+
+ // Make sure the lease is in the database and hostname is correct.
+ lease = LeaseMgrFactory::instance().getLease4(IOAddress(expected_address));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(expected_fqdn, lease->hostname_);
+
+}
+
+// Verifies that the replace-client-name behavior is correct for each of
+// the supported modes.
+TEST_F(NameDhcpv4SrvTest, replaceClientNameModeTest) {
+
+ 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 default hostname-char-set sanitizes Hostname option
+// values received from clients.
+TEST_F(NameDhcpv4SrvTest, sanitizeHostDefault) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Configure DHCP server.
+ configure(CONFIGS[2], *client.getServer());
+
+ // Make sure that DDNS is not enabled.
+ ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+
+ struct Scenario {
+ std::string description_;
+ std::string original_;
+ std::string sanitized_;
+ };
+
+ std::vector<Scenario> scenarios = {
+ {
+ "unqualified host name with invalid characters",
+ "one-&$_-host",
+ "one--host.fake-suffix.isc.org"
+ },
+ {
+ "qualified host name with invalid characters",
+ "two--host.other.org",
+ "two--host.other.org"
+ },
+ {
+ "unqualified host name with all valid characters",
+ "three-ok-host",
+ "three-ok-host.fake-suffix.isc.org"
+ },
+ {
+ "qualified host name with valid characters",
+ "four-ok-host.other.org",
+ "four-ok-host.other.org"
+ },
+ {
+ "qualified host name with nuls",
+ std::string("four-ok-host\000.other.org",23),
+ "four-ok-host.other.org"
+ }
+ };
+
+ Pkt4Ptr resp;
+ OptionStringPtr hostname;
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE((scenario).description_);
+ {
+ // Set the hostname option.
+ ASSERT_NO_THROW(client.includeHostname((scenario).original_));
+
+ // Send the DHCPDISCOVER and make sure that the server responded.
+ ASSERT_NO_THROW(client.doDiscover());
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+
+ // Make sure the response hostname is what we expect.
+ hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
+ ASSERT_TRUE(hostname);
+ EXPECT_EQ((scenario).sanitized_, hostname->getValue());
+ }
+ }
+}
+
+
+// Verifies that setting hostname-char-set sanitizes Hostname option
+// values received from clients.
+TEST_F(NameDhcpv4SrvTest, sanitizeHost) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Configure DHCP server.
+ configure(CONFIGS[6], *client.getServer());
+
+ // Make sure that DDNS is enabled.
+ ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+ ASSERT_NO_THROW(client.getServer()->startD2());
+
+ struct Scenario {
+ std::string description_;
+ std::string original_;
+ std::string sanitized_;
+ };
+
+ std::vector<Scenario> scenarios = {
+ {
+ "unqualified host name with invalid characters",
+ "one-&$_-host",
+ "one-xxx-host.example.com"
+ },
+ {
+ "qualified host name with invalid characters",
+ "two-&$_-host.other.org",
+ "two-xxx-host.other.org"
+ },
+ {
+ "unqualified host name with all valid characters",
+ "three-ok-host",
+ "three-ok-host.example.com"
+ },
+ {
+ "qualified host name with valid characters",
+ "four-ok-host.other.org",
+ "four-ok-host.other.org"
+ },
+ {
+ "qualified host name with nuls",
+ std::string("four-ok-host\000.other.org",23),
+ "four-ok-hostx.other.org"
+ }
+ };
+
+ Pkt4Ptr resp;
+ OptionStringPtr hostname;
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE((scenario).description_);
+ {
+ // Set the hostname option.
+ ASSERT_NO_THROW_LOG(client.includeHostname((scenario).original_));
+
+ // Send the DHCPDISCOVER and make sure that the server responded.
+ ASSERT_NO_THROW(client.doDiscover());
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+
+ // Make sure the response hostname is what we expect.
+ hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
+ ASSERT_TRUE(hostname);
+ EXPECT_EQ((scenario).sanitized_, hostname->getValue());
+ }
+ }
+}
+
+// Verifies that setting global hostname-char-set sanitizes Hostname option
+// values received from clients.
+TEST_F(NameDhcpv4SrvTest, sanitizeHostGlobal) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Configure DHCP server.
+ configure(CONFIGS[7], *client.getServer());
+
+ // Make sure that DDNS is not enabled.
+ ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+
+ struct Scenario {
+ std::string description_;
+ std::string original_;
+ std::string sanitized_;
+ };
+
+ std::vector<Scenario> scenarios = {
+ {
+ "unqualified host name with invalid characters",
+ "one-&$_-host",
+ "one-xxx-host"
+ },
+ {
+ "qualified host name with invalid characters",
+ "two-&$_-host.other.org",
+ "two-xxx-host.other.org"
+ },
+ {
+ "unqualified host name with all valid characters",
+ "three-ok-host",
+ "three-ok-host"
+ },
+ {
+ "qualified host name with valid characters",
+ "four-ok-host.other.org",
+ "four-ok-host.other.org"
+ }
+ };
+
+ Pkt4Ptr resp;
+ OptionStringPtr hostname;
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE((scenario).description_);
+ {
+ // Set the hostname option.
+ ASSERT_NO_THROW(client.includeHostname((scenario).original_));
+
+ // Send the DHCPDISCOVER and make sure that the server responded.
+ ASSERT_NO_THROW(client.doDiscover());
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+
+ // Make sure the response hostname is what we expect.
+ hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
+ ASSERT_TRUE(hostname);
+ EXPECT_EQ((scenario).sanitized_, hostname->getValue());
+ }
+ }
+}
+
+// Verifies that setting hostname-char-set sanitizes FQDN option
+// values received from clients.
+TEST_F(NameDhcpv4SrvTest, sanitizeFqdn) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Configure DHCP server.
+ configure(CONFIGS[6], *client.getServer());
+
+ // Make sure that DDNS is enabled.
+ ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+ ASSERT_NO_THROW(client.getServer()->startD2());
+
+ struct Scenario {
+ std::string description_;
+ std::string original_;
+ Option4ClientFqdn::DomainNameType name_type_;
+ std::string sanitized_;
+ };
+
+ std::vector<Scenario> scenarios = {
+ {
+ "unqualified FQDN with invalid characters",
+ "one-&*_-host",
+ Option4ClientFqdn::PARTIAL,
+ "one-xxx-host.example.com."
+ },
+ {
+ "qualified FQDN with invalid characters",
+ "two-&*_-host.other.org",
+ Option4ClientFqdn::FULL,
+ "two-xxx-host.other.org."
+ },
+ {
+ "unqualified FQDN name with all valid characters",
+ "three-ok-host",
+ Option4ClientFqdn::PARTIAL,
+ "three-ok-host.example.com."
+ },
+ {
+ "qualified FQDN name with valid characters",
+ "four-ok-host.other.org",
+ Option4ClientFqdn::FULL,
+ "four-ok-host.other.org."
+ }
+ };
+
+ Pkt4Ptr resp;
+ Option4ClientFqdnPtr fqdn;
+ for (auto scenario = scenarios.begin(); scenario != scenarios.end(); ++scenario) {
+ SCOPED_TRACE((*scenario).description_);
+ {
+ // Set the hostname option.
+ ASSERT_NO_THROW(client.includeHostname((*scenario).original_));
+ ASSERT_NO_THROW(client.includeFQDN(0, (*scenario).original_, (*scenario).name_type_));
+
+ // Send the DHCPDISCOVER and make sure that the server responded.
+ ASSERT_NO_THROW(client.doDiscover());
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+
+ // Make sure the response fqdn is what we expect.
+ fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
+ ASSERT_TRUE(fqdn);
+ EXPECT_EQ((*scenario).sanitized_, fqdn->getDomainName());
+ }
+ }
+}
+
+// Verifies that setting global hostname-char-set sanitizes FQDN option
+// values received from clients.
+TEST_F(NameDhcpv4SrvTest, sanitizeFqdnGlobal) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Configure DHCP server.
+ configure(CONFIGS[7], *client.getServer());
+
+ // Make sure that DDNS is not enabled.
+ ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+
+ struct Scenario {
+ std::string description_;
+ std::string original_;
+ Option4ClientFqdn::DomainNameType name_type_;
+ std::string sanitized_;
+ };
+
+ std::vector<Scenario> scenarios = {
+ {
+ "unqualified FQDN with invalid characters",
+ "one-&*_-host",
+ Option4ClientFqdn::PARTIAL,
+ "one-xxx-host."
+ },
+ {
+ "qualified FQDN with invalid characters",
+ "two-&*_-host.other.org",
+ Option4ClientFqdn::FULL,
+ "two-xxx-host.other.org."
+ },
+ {
+ "unqualified FQDN name with all valid characters",
+ "three-ok-host",
+ Option4ClientFqdn::PARTIAL,
+ "three-ok-host."
+ },
+ {
+ "qualified FQDN name with valid characters",
+ "four-ok-host.other.org",
+ Option4ClientFqdn::FULL,
+ "four-ok-host.other.org."
+ },
+ {
+ "qualified FQDN name with nuls",
+ std::string("four-ok-host.ot\000\000her.org", 24),
+ Option4ClientFqdn::FULL,
+ "four-ok-host.otxxher.org."
+ }
+ };
+
+ Pkt4Ptr resp;
+ Option4ClientFqdnPtr fqdn;
+ for (auto scenario = scenarios.begin(); scenario != scenarios.end(); ++scenario) {
+ SCOPED_TRACE((*scenario).description_);
+ {
+ // Set the hostname option.
+ ASSERT_NO_THROW(client.includeHostname((*scenario).original_));
+ ASSERT_NO_THROW(client.includeFQDN(0, (*scenario).original_, (*scenario).name_type_));
+
+ // Send the DHCPDISCOVER and make sure that the server responded.
+ ASSERT_NO_THROW(client.doDiscover());
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+
+ // Make sure the response fqdn is what we expect.
+ fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
+ ASSERT_TRUE(fqdn);
+ EXPECT_EQ((*scenario).sanitized_, fqdn->getDomainName());
+ }
+ }
+}
+
+// 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(NameDhcpv4SrvTest, ddnsScopeTest) {
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ client1.setIfaceName("eth0");
+ client1.setIfaceIndex(ETH0_INDEX);
+
+ // Load a configuration with D2 enabled
+ ASSERT_NO_FATAL_FAILURE(configure(CONFIGS[8], *client1.getServer()));
+ ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+
+ // Include the Client FQDN option.
+ ASSERT_NO_THROW(client1.includeFQDN((Option4ClientFqdn::FLAG_S
+ | Option4ClientFqdn::FLAG_E),
+ "client1.example.com.",
+ Option4ClientFqdn::FULL));
+
+ // Now send the DHCPREQUEST with including the FQDN option.
+ ASSERT_NO_THROW(client1.doDORA());
+ Pkt4Ptr resp = client1.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Check that the response FQDN is as expected.
+ Option4ClientFqdnPtr fqdn;
+ fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
+ ASSERT_TRUE(fqdn);
+ EXPECT_EQ("client1.example.com.", 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.
+ Dhcp4Client client2(Dhcp4Client::SELECTING);
+ client2.setIfaceName("eth1");
+ client2.setIfaceIndex(ETH1_INDEX);
+
+ // Include the Client FQDN option.
+ ASSERT_NO_THROW(client2.includeFQDN((Option4ClientFqdn::FLAG_S
+ | Option4ClientFqdn::FLAG_E),
+ "two.example.com.",
+ Option4ClientFqdn::FULL));
+
+ ASSERT_NO_THROW(client2.doDORA());
+ resp = client2.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Check that the response FQDN is as expected.
+ fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
+ ASSERT_TRUE(fqdn);
+ EXPECT_EQ("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,
+ resp->getYiaddr().toText(),
+ "two.example.com.", "",
+ time(NULL), 7200, true);
+}
+
+// Verifies that when reusing an expired lease, whether or not it is given to its
+// original owner or not, appropriate DNS updates are done if needed.
+TEST_F(NameDhcpv4SrvTest, processReuseExpired) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Configure with a one address pool with DDNS enabled.
+ configure(CONFIGS[9], *srv_);
+
+ struct Scenario {
+ std::string label_;
+ std::string client_id1_;
+ std::string dhcid1_;
+ std::string hostname1_;
+
+ std::string client_id2_;
+ std::string dhcid2_;
+ std::string hostname2_;
+ bool expect_remove_;
+ bool expect_add_;
+ };
+
+ std::string cid1 = "11:11:11:11";
+ std::string dhcid1 = "0001013904F56A5BD4E926EB6BC36C825CEA1159FF2AFBE28E4391E67CC040F6A35785";
+ std::string cid2 = "22:22:22:22";
+ std::string dhcid2 = "000101459343356AC37A73A372ECE989F9C4397E7FBBD6658239EA4B3B77B6B904A46F";
+ bool remove = true;
+ bool add = true;
+
+ std::vector<Scenario> scenarios = {
+ {
+ "same client, hostname added",
+ cid1, dhcid1, "",
+ cid1, dhcid1, "one.example.com.",
+ !remove, add
+ },
+ {
+ "same client, same host",
+ cid1, dhcid1, "one.example.com.",
+ cid1, dhcid1, "one.example.com.",
+ remove, add
+ },
+ {
+ "same client, hostname removed",
+ cid1, dhcid1, "one.example.com.",
+ cid1, dhcid1, "",
+ remove, !add
+ },
+ {
+ "different client, hostname added",
+ cid1, dhcid1, "",
+ cid2, dhcid2, "two.example.com.",
+ !remove, add
+ },
+ {
+ "different client, different host",
+ cid1, dhcid1, "one.example.com.",
+ cid2, dhcid2, "two.example.com.",
+ remove, add
+ },
+ {
+ "different client, hostname removed",
+ cid1, dhcid1, "one.example.com.",
+ cid2, dhcid2, "",
+ remove, !add
+ }
+ };
+
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.label_);
+ {
+ // Create the original leasing client.
+ boost::shared_ptr<Dhcp4Client> client1(new Dhcp4Client(srv_, Dhcp4Client::SELECTING));
+ client1->setIfaceName("eth1");
+ client1->setIfaceIndex(ETH1_INDEX);
+ client1->includeClientId(scenario.client_id1_);
+ if (!scenario.hostname1_.empty()) {
+ ASSERT_NO_THROW(client1->includeHostname(scenario.hostname1_));
+ }
+
+ ASSERT_NO_THROW(client1->doDORA());
+ Pkt4Ptr resp = client1->getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Verify that there is one NameChangeRequest generated.
+ if (scenario.hostname1_.empty()) {
+ ASSERT_EQ(0, d2_mgr_.getQueueSize());
+ } else {
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD,
+ true, true,
+ resp->getYiaddr().toText(), scenario.hostname1_,
+ scenario.dhcid1_,
+ time(NULL), subnet_->getValid(), true);
+ }
+
+ // Expire the lease
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(resp->getYiaddr());
+ ASSERT_TRUE(lease);
+ lease->cltt_ = 0;
+ ASSERT_NO_THROW(LeaseMgrFactory::instance().updateLease4(lease));
+ lease = LeaseMgrFactory::instance().getLease4(resp->getYiaddr());
+ ASSERT_TRUE(lease->expired());
+
+ // Create the requesting/returning client.
+ boost::shared_ptr<Dhcp4Client> client2;
+ if (scenario.client_id1_ == scenario.client_id2_) {
+ client2 = client1;
+ } else {
+ client2.reset(new Dhcp4Client(srv_, Dhcp4Client::SELECTING));
+ client2->setIfaceName("eth1");
+ client2->setIfaceIndex(ETH1_INDEX);
+ client2->includeClientId(scenario.client_id2_);
+ }
+
+ ASSERT_NO_THROW(client2->includeHostname(scenario.hostname2_));
+
+ ASSERT_NO_THROW(client2->doDORA());
+ resp = client2->getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Verify that there is one NameChangeRequest generated.
+ size_t expected_count = (scenario.expect_remove_ ? 1 : 0) +
+ (scenario.expect_add_ ? 1 : 0);
+
+ ASSERT_EQ(expected_count, d2_mgr_.getQueueSize());
+ if (scenario.expect_remove_) {
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE,
+ true, true,
+ resp->getYiaddr().toText(), scenario.hostname1_,
+ scenario.dhcid1_,
+ time(NULL), subnet_->getValid(), true);
+ }
+
+ if (scenario.expect_add_) {
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD,
+ true, true,
+ resp->getYiaddr().toText(), scenario.hostname2_,
+ scenario.dhcid2_,
+ time(NULL), subnet_->getValid(), true);
+ }
+
+ bool deleted = false;
+ ASSERT_NO_THROW(deleted = LeaseMgrFactory::instance().deleteLease(lease));
+ ASSERT_TRUE(deleted);
+ }
+ }
+}
+
+// Verifies that the DDNS parameters used for a lease in subnet in
+// shared-network belong to lease's subnet. This checks that we
+// get the right results even when the allocation engine changes the
+// subnet choice.
+TEST_F(NameDhcpv4SrvTest, ddnsSharedNetworkTest) {
+ // Load a configuration with D2 enabled
+ ASSERT_NO_FATAL_FAILURE(configure(CONFIGS[10], *srv_));
+ ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+
+ // Create a client and get a lease.
+ Dhcp4Client client1(srv_, Dhcp4Client::SELECTING);
+ client1.setIfaceName("eth1");
+ client1.setIfaceIndex(ETH1_INDEX);
+ ASSERT_NO_THROW(client1.includeHostname("client1"));
+
+ // Do the DORA.
+ ASSERT_NO_THROW(client1.doDORA());
+ Pkt4Ptr resp = client1.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Obtain the Hostname option sent in the response and make sure that the server
+ // has used the hostname reserved for this client.
+ OptionStringPtr hostname;
+ hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
+ ASSERT_TRUE(hostname);
+ EXPECT_EQ("client1.one.example.com", hostname->getValue());
+
+ // Make sure the lease is in the database and hostname is correct.
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(IOAddress("192.0.2.10"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("client1.one.example.com", lease->hostname_);
+
+ // Verify that an NCR was generated correctly.
+ ASSERT_EQ(1, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ resp->getYiaddr().toText(),
+ "client1.one.example.com.", "",
+ time(NULL), 7200, true);
+
+ // Now let's try with a second client. The first subnet is full so we should
+ // end up on the second subnet.
+ Dhcp4Client client2(srv_, Dhcp4Client::SELECTING);
+ client2.setIfaceName("eth1");
+ client2.setIfaceIndex(ETH1_INDEX);
+ ASSERT_NO_THROW(client2.includeHostname("client2"));
+
+ ASSERT_NO_THROW(client2.doDORA());
+ resp = client2.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Obtain the Hostname option sent in the response and make sure that the server
+ // has used the hostname reserved for this client.
+ hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
+ ASSERT_TRUE(hostname);
+ EXPECT_EQ("client2.two.example.com", hostname->getValue());
+
+ // Verify the NCR is there and correct.
+ ASSERT_EQ(1, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ resp->getYiaddr().toText(),
+ "client2.two.example.com.", "",
+ time(NULL), 7200, true);
+
+ // Make sure the lease is in the database and hostname is correct.
+ lease = LeaseMgrFactory::instance().getLease4(IOAddress("10.0.0.10"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("client2.two.example.com", lease->hostname_);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/get_config_unittest.cc b/src/bin/dhcp4/tests/get_config_unittest.cc
new file mode 100644
index 0000000..68941a6
--- /dev/null
+++ b/src/bin/dhcp4/tests/get_config_unittest.cc
@@ -0,0 +1,12340 @@
+// 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_parser4.h>
+#include <dhcp4/dhcp4_srv.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/json_config_parser.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp4/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 dhcp4_unittests on Dhcp4ParserTest
+/// redirecting the standard error to a temporary file, e.g. by
+/// @code
+/// ./dhcp4_unittests --gtest_filter="Dhcp4Parser*" > /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 dhcp4_unittests on Dhcp4GetConfigTest
+/// redirecting the standard error to a temporary file, e.g. by
+/// @code
+/// ./dhcp4_unittests --gtest_filter="Dhcp4GetConfig*" > /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"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [ ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 1
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 2
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 3
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"max-valid-lifetime\": 5000,\n"
+" \"min-valid-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 4
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" },\n"
+" {\n"
+" \"id\": 0,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.3.101 - 192.0.3.150\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.3.0/24\"\n"
+" },\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.4.101 - 192.0.4.150\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.4.0/24\"\n"
+" },\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.5.101 - 192.0.5.150\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.5.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 5
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"id\": 1024,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" },\n"
+" {\n"
+" \"id\": 100,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.3.101 - 192.0.3.150\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.3.0/24\"\n"
+" },\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.4.101 - 192.0.4.150\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.4.0/24\"\n"
+" },\n"
+" {\n"
+" \"id\": 34,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.5.101 - 192.0.5.150\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.5.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 6
+"{\n"
+" \"boot-file-name\": \"bar\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"next-server\": \"1.2.3.4\",\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"server-hostname\": \"foo\",\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 7
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"boot-file-name\": \"bar\",\n"
+" \"next-server\": \"1.2.3.4\",\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"server-hostname\": \"foo\",\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 8
+"{\n"
+" \"boot-file-name\": \"nofile\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"next-server\": \"192.0.0.1\",\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"server-hostname\": \"nohost\",\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"boot-file-name\": \"bootfile.efi\",\n"
+" \"next-server\": \"1.2.3.4\",\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"server-hostname\": \"some-name.example.org\",\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 9
+"{\n"
+" \"echo-client-id\": false,\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 10
+"{\n"
+" \"echo-client-id\": true,\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 11
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"match-client-id\": true,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" },\n"
+" {\n"
+" \"match-client-id\": false,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.3.1 - 192.0.3.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.3.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 12
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"match-client-id\": true,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"match-client-id\": false,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" },\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.3.1 - 192.0.3.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.3.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 13
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"authoritative\": true,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" },\n"
+" {\n"
+" \"authoritative\": false,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.3.1 - 192.0.3.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.3.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 14
+"{\n"
+" \"authoritative\": true,\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"authoritative\": false,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" },\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.3.1 - 192.0.3.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.3.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 15
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"max-valid-lifetime\": 5000,\n"
+" \"min-valid-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"max-valid-lifetime\": 5,\n"
+" \"min-valid-lifetime\": 3,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2,\n"
+" \"renew-timer\": 1,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"valid-lifetime\": 4\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 16
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.0/28\"\n"
+" },\n"
+" {\n"
+" \"pool\": \"192.0.2.200-192.0.2.255\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" },\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.3.0/25\"\n"
+" },\n"
+" {\n"
+" \"pool\": \"192.0.3.128/25\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.3.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 17
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.128/28\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 18
+"{\n"
+" \"option-def\": [\n"
+" {\n"
+" \"code\": 100,\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"ipv4-address\"\n"
+" }\n"
+" ]\n"
+" }\n",
+ // CONFIGURATION 19
+"{\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 20
+"{\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 21
+"{\n"
+" \"option-def\": [\n"
+" {\n"
+" \"array\": true,\n"
+" \"code\": 100,\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"uint32\"\n"
+" }\n"
+" ]\n"
+" }\n",
+ // CONFIGURATION 22
+"{\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 23
+"{\n"
+" \"option-def\": [\n"
+" {\n"
+" \"code\": 109,\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"dhcp4\",\n"
+" \"type\": \"string\"\n"
+" }\n"
+" ]\n"
+" }\n",
+ // CONFIGURATION 24
+"{\n"
+" \"option-def\": [\n"
+" {\n"
+" \"code\": 170,\n"
+" \"name\": \"unassigned-option-170\",\n"
+" \"space\": \"dhcp4\",\n"
+" \"type\": \"string\"\n"
+" }\n"
+" ]\n"
+" }\n",
+ // CONFIGURATION 25
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"option-data\": [\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"dhcp-message\"\n"
+" },\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"01\",\n"
+" \"name\": \"default-ip-ttl\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 26
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"option-data\": [\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"dhcp-message\"\n"
+" },\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"01\",\n"
+" \"name\": \"default-ip-ttl\"\n"
+" }\n"
+" ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\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"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"dhcp-message\"\n"
+" },\n"
+" {\n"
+" \"data\": \"1234\",\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"code\": 56,\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"uint32\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 28
+"{\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\": 1,\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"uint32\"\n"
+" },\n"
+" {\n"
+" \"code\": 2,\n"
+" \"name\": \"foo2\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"ipv4-address\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 29
+"{\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\": 222,\n"
+" \"encapsulate\": \"isc\",\n"
+" \"name\": \"base-option\",\n"
+" \"space\": \"dhcp4\",\n"
+" \"type\": \"uint8\"\n"
+" },\n"
+" {\n"
+" \"code\": 1,\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"uint32\"\n"
+" },\n"
+" {\n"
+" \"code\": 2,\n"
+" \"name\": \"foo2\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"ipv4-address\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 3000\n"
+" }\n",
+ // CONFIGURATION 30
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"option-data\": [\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"AB\",\n"
+" \"name\": \"dhcp-message\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"option-data\": [\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"dhcp-message\"\n"
+" },\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"01\",\n"
+" \"name\": \"default-ip-ttl\"\n"
+" }\n"
+" ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 31
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"option-data\": [\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"0102030405060708090A\",\n"
+" \"name\": \"dhcp-message\"\n"
+" }\n"
+" ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" },\n"
+" {\n"
+" \"option-data\": [\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"FF\",\n"
+" \"name\": \"default-ip-ttl\"\n"
+" }\n"
+" ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.3.101 - 192.0.3.150\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.3.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 32
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"dhcp-message\"\n"
+" },\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"01\",\n"
+" \"name\": \"default-ip-ttl\"\n"
+" }\n"
+" ],\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 33
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"dhcp-message\"\n"
+" }\n"
+" ],\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" },\n"
+" {\n"
+" \"option-data\": [\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"01\",\n"
+" \"name\": \"default-ip-ttl\"\n"
+" }\n"
+" ],\n"
+" \"pool\": \"192.0.2.200 - 192.0.2.250\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 34
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"option-data\": [\n"
+" {\n"
+" \"data\": \"true, 10.0.0.3, 127.0.0.1\",\n"
+" \"name\": \"slp-directory-agent\"\n"
+" },\n"
+" {\n"
+" \"data\": \"false, \",\n"
+" \"name\": \"slp-service-scope\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 35
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"option-data\": [\n"
+" {\n"
+" \"data\": \"1234\",\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"vendor-encapsulated-options-space\"\n"
+" },\n"
+" {\n"
+" \"data\": \"192.168.2.1\",\n"
+" \"name\": \"foo2\",\n"
+" \"space\": \"vendor-encapsulated-options-space\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"code\": 1,\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"vendor-encapsulated-options-space\",\n"
+" \"type\": \"uint32\"\n"
+" },\n"
+" {\n"
+" \"code\": 2,\n"
+" \"name\": \"foo2\",\n"
+" \"space\": \"vendor-encapsulated-options-space\",\n"
+" \"type\": \"ipv4-address\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 36
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"option-data\": [\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"name\": \"vendor-encapsulated-options\"\n"
+" },\n"
+" {\n"
+" \"data\": \"1234\",\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"vendor-encapsulated-options-space\"\n"
+" },\n"
+" {\n"
+" \"code\": 2,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"192.168.2.1\",\n"
+" \"name\": \"foo2\",\n"
+" \"space\": \"vendor-encapsulated-options-space\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"code\": 1,\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"vendor-encapsulated-options-space\",\n"
+" \"type\": \"uint32\"\n"
+" },\n"
+" {\n"
+" \"code\": 2,\n"
+" \"name\": \"foo2\",\n"
+" \"space\": \"vendor-encapsulated-options-space\",\n"
+" \"type\": \"ipv4-address\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 3000\n"
+" }\n",
+ // CONFIGURATION 37
+"{\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"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1-192.0.2.10\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 38
+"{\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"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 39
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"eth0\", \"eth1\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 40
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"eth0\", \"*\", \"eth1\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 41
+"{\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": true,\n"
+" \"max-queue-size\": 2048,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"192.168.2.2\",\n"
+" \"sender-port\": 778,\n"
+" \"server-ip\": \"192.168.2.1\",\n"
+" \"server-port\": 777\n"
+" },\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 42
+"{\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\": \"192.168.2.2\",\n"
+" \"sender-port\": 778,\n"
+" \"server-ip\": \"192.168.2.1\",\n"
+" \"server-port\": 777\n"
+" },\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 43
+"{\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\": \"192.168.2.2\",\n"
+" \"sender-port\": 778,\n"
+" \"server-ip\": \"192.168.2.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"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 44
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2,\n"
+" \"relay\": {\n"
+" \"ip-address\": \"192.0.2.123\"\n"
+" },\n"
+" \"renew-timer\": 1,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"valid-lifetime\": 4\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 45
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ \"192.0.3.123\", \"192.0.3.124\" ]\n"
+" },\n"
+" \"renew-timer\": 1,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"valid-lifetime\": 4\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 46
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"client-class\": \"alpha\",\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" },\n"
+" {\n"
+" \"client-class\": \"beta\",\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.3.101 - 192.0.3.150\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.3.0/24\"\n"
+" },\n"
+" {\n"
+" \"client-class\": \"gamma\",\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.4.101 - 192.0.4.150\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.4.0/24\"\n"
+" },\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.5.101 - 192.0.5.150\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.5.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 47
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"client-class\": \"alpha\",\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" },\n"
+" {\n"
+" \"client-class\": \"beta\",\n"
+" \"pool\": \"192.0.3.101 - 192.0.3.150\"\n"
+" },\n"
+" {\n"
+" \"client-class\": \"gamma\",\n"
+" \"pool\": \"192.0.4.101 - 192.0.4.150\"\n"
+" },\n"
+" {\n"
+" \"pool\": \"192.0.5.101 - 192.0.5.150\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.0.0/16\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 48
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"id\": 123,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"reservations\": [ ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" },\n"
+" {\n"
+" \"id\": 234,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.3.101 - 192.0.3.150\"\n"
+" }\n"
+" ],\n"
+" \"reservations\": [\n"
+" {\n"
+" \"duid\": \"01:02:03:04:05:06:07:08:09:0A\",\n"
+" \"hostname\": \"\",\n"
+" \"ip-address\": \"192.0.3.112\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"data\": \"192.0.3.15\",\n"
+" \"name\": \"name-servers\"\n"
+" },\n"
+" {\n"
+" \"data\": \"32\",\n"
+" \"name\": \"default-ip-ttl\"\n"
+" }\n"
+" ]\n"
+" },\n"
+" {\n"
+" \"hostname\": \"\",\n"
+" \"hw-address\": \"01:02:03:04:05:06\",\n"
+" \"ip-address\": \"192.0.3.120\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"data\": \"192.0.3.95\",\n"
+" \"name\": \"name-servers\"\n"
+" },\n"
+" {\n"
+" \"data\": \"11\",\n"
+" \"name\": \"default-ip-ttl\"\n"
+" }\n"
+" ]\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.3.0/24\"\n"
+" },\n"
+" {\n"
+" \"id\": 542,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.4.101 - 192.0.4.150\"\n"
+" }\n"
+" ],\n"
+" \"reservations\": [\n"
+" {\n"
+" \"duid\": \"0A:09:08:07:06:05:04:03:02:01\",\n"
+" \"hostname\": \"\",\n"
+" \"ip-address\": \"192.0.4.101\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"data\": \"192.0.4.11\",\n"
+" \"name\": \"name-servers\"\n"
+" },\n"
+" {\n"
+" \"data\": \"95\",\n"
+" \"name\": \"default-ip-ttl\"\n"
+" }\n"
+" ]\n"
+" },\n"
+" {\n"
+" \"circuit-id\": \"060504030201\",\n"
+" \"hostname\": \"\",\n"
+" \"ip-address\": \"192.0.4.102\"\n"
+" },\n"
+" {\n"
+" \"client-id\": \"05:01:02:03:04:05:06\",\n"
+" \"hostname\": \"\",\n"
+" \"ip-address\": \"192.0.4.103\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.4.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 49
+"{\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"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"id\": 234,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.3.101 - 192.0.3.150\"\n"
+" }\n"
+" ],\n"
+" \"reservations\": [\n"
+" {\n"
+" \"duid\": \"01:02:03:04:05:06:07:08:09:0A\",\n"
+" \"ip-address\": \"192.0.3.112\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"data\": \"123\",\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\"\n"
+" }\n"
+" ]\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.3.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 50
+"{\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.1.0/24\"\n"
+" }\n"
+" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"subnet\": \"192.0.1.0/24\"\n"
+" },\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-out-of-pool\": true,\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" },\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.3.0/24\"\n"
+" }\n"
+" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": false,\n"
+" \"subnet\": \"192.0.3.0/24\"\n"
+" },\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.4.0/24\"\n"
+" }\n"
+" ],\n"
+" \"reservations-global\": true,\n"
+" \"reservations-in-subnet\": false,\n"
+" \"subnet\": \"192.0.4.0/24\"\n"
+" },\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.5.0/24\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.5.0/24\"\n"
+" },\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.6.0/24\"\n"
+" }\n"
+" ],\n"
+" \"reservations-global\": true,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"subnet\": \"192.0.6.0/24\"\n"
+" },\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.7.0/24\"\n"
+" }\n"
+" ],\n"
+" \"reservations-global\": true,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-out-of-pool\": true,\n"
+" \"subnet\": \"192.0.7.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 51
+"{\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-out-of-pool\": true,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" },\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.3.0/24\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.3.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 52
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"subnet4\": [ ]\n"
+" }\n",
+ // CONFIGURATION 53
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"subnet4\": [ ]\n"
+" }\n",
+ // CONFIGURATION 54
+"{\n"
+" \"decline-probation-period\": 12345,\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"subnet4\": [ ]\n"
+" }\n",
+ // CONFIGURATION 55
+"{\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"
+" \"subnet4\": [ ]\n"
+" }\n",
+ // CONFIGURATION 56
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 57
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-subnet\": \"2001:db8::123/45\",\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 58
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"ethX\",\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 59
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"ethX\",\n"
+" \"4o6-subnet\": \"2001:db8::543/21\",\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 60
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface-id\": \"vlan123\",\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 61
+"{\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"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 62
+"{\n"
+" \"client-classes\": [\n"
+" {\n"
+" \"max-valid-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 1000,\n"
+" \"name\": \"one\",\n"
+" \"valid-lifetime\": 2000\n"
+" },\n"
+" {\n"
+" \"name\": \"two\"\n"
+" }\n"
+" ],\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ]\n"
+" }\n",
+ // CONFIGURATION 63
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.0/28\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 64
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.0/28\",\n"
+" \"user-context\": { }\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 65
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.0/28\",\n"
+" \"user-context\": {\n"
+" \"bool-param\": true,\n"
+" \"integer-param\": 42,\n"
+" \"string-param\": \"Sagittarius\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 66
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.0 - 192.0.2.15\",\n"
+" \"user-context\": {\n"
+" \"bool-param\": true,\n"
+" \"integer-param\": 42,\n"
+" \"string-param\": \"Sagittarius\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 67
+"{\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"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 68
+"{\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/kea4-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\": \"dhcp-message\",\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\": \"ipv4-address\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"An option definition\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"shared-networks\": [\n"
+" {\n"
+" \"name\": \"foo\",\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"id\": 100,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.1.1-192.0.1.10\",\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-name\",\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\": \"192.0.1.0/24\",\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 DHCPv4 server\"\n"
+" }\n"
+" }\n",
+ // CONFIGURATION 69
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\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\": \"global1\",\n"
+" \"ip-address\": \"192.0.200.1\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"data\": \"192.0.3.15\",\n"
+" \"name\": \"name-servers\"\n"
+" },\n"
+" {\n"
+" \"data\": \"32\",\n"
+" \"name\": \"default-ip-ttl\"\n"
+" }\n"
+" ]\n"
+" },\n"
+" {\n"
+" \"hostname\": \"global2\",\n"
+" \"hw-address\": \"01:02:03:04:05:06\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"data\": \"192.0.3.95\",\n"
+" \"name\": \"name-servers\"\n"
+" },\n"
+" {\n"
+" \"data\": \"11\",\n"
+" \"name\": \"default-ip-ttl\"\n"
+" }\n"
+" ]\n"
+" }\n"
+" ],\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"id\": 123,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"reservations\": [ ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" },\n"
+" {\n"
+" \"id\": 542,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.4.101 - 192.0.4.150\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.4.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 70
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"shared-networks\": [\n"
+" {\n"
+" \"calculate-tee-times\": true,\n"
+" \"name\": \"foo\",\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 100,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.1.1-192.0.1.10\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.1.0/24\",\n"
+" \"t1-percent\": 0.45,\n"
+" \"t2-percent\": 0.65\n"
+" },\n"
+" {\n"
+" \"id\": 200,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1-192.0.2.10\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.4,\n"
+" \"t2-percent\": 0.75\n"
+" }\n"
+" ],\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"id\": 300,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.3.0 - 192.0.3.15\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.3.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 71
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"store-extended-info\": true,\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" },\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.3.1 - 192.0.3.100\"\n"
+" }\n"
+" ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.3.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 72
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"store-extended-info\": true,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\"\n"
+" },\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"192.0.3.1 - 192.0.3.100\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"192.0.3.0/24\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 73
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\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 74
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"subnet4\": [ ]\n"
+" }\n",
+ // CONFIGURATION 75
+"{\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"
+" \"subnet4\": [ ]\n"
+" }\n",
+ // CONFIGURATION 76
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n,"
+" \"service-sockets-require-all\": true\n"
+" },\n"
+" \"subnet4\": [ ]\n"
+" }\n",
+ // CONFIGURATION 77
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false,\n"
+" \"service-sockets-max-retries\": 10\n"
+" },\n"
+" \"subnet4\": [ ]\n"
+" }\n",
+ // CONFIGURATION 78
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false,\n"
+" \"service-sockets-max-retries\": 10,\n"
+" \"service-sockets-require-all\": true,\n"
+" \"service-sockets-retry-wait-time\": 1000\n"
+" },\n"
+" \"subnet4\": [ ]\n"
+" }\n"
+};
+
+/// @brief unparsed configurations
+const char* UNPARSED_CONFIGS[] = {
+ // CONFIGURATION 0
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 1
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 2
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 3
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"max-valid-lifetime\": 5000,\n"
+" \"min-valid-lifetime\": 3000,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 5000,\n"
+" \"min-valid-lifetime\": 3000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 4
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 2,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.3.101-192.0.3.150\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.3.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 3,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.4.101-192.0.4.150\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.4.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 4,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.5.101-192.0.5.150\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.5.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 5
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.4.101-192.0.4.150\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.4.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 34,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.5.101-192.0.5.150\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.5.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 100,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.3.101-192.0.3.150\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.3.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1024,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 6
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"bar\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"1.2.3.4\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"foo\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 7
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"boot-file-name\": \"bar\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"next-server\": \"1.2.3.4\",\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"server-hostname\": \"foo\",\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 8
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"nofile\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"192.0.0.1\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"nohost\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"boot-file-name\": \"bootfile.efi\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"next-server\": \"1.2.3.4\",\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"server-hostname\": \"some-name.example.org\",\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 9
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": 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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 10
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 11
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"match-client-id\": true,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 2,\n"
+" \"match-client-id\": false,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.3.1-192.0.3.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.3.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 12
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"match-client-id\": false,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 2,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.3.1-192.0.3.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.3.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 13
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"authoritative\": true,\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"authoritative\": false,\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 2,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.3.1-192.0.3.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.3.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 14
+"{\n"
+" \"authoritative\": true,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"authoritative\": false,\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 2,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.3.1-192.0.3.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.3.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 15
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"max-valid-lifetime\": 5000,\n"
+" \"min-valid-lifetime\": 3000,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 5,\n"
+" \"min-valid-lifetime\": 3,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 16
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.0/28\"\n"
+" },\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.200-192.0.2.255\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 2,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.3.0/25\"\n"
+" },\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.3.128/25\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.3.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 17
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.128/28\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 18
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\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\": \"ipv4-address\"\n"
+" }\n"
+" ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 19
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\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"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 20
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\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"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 21
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\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"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 22
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\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"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 23
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 109,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"dhcp4\",\n"
+" \"type\": \"string\"\n"
+" }\n"
+" ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 24
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 170,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"unassigned-option-170\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"dhcp4\",\n"
+" \"type\": \"string\"\n"
+" }\n"
+" ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 25
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 56,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"dhcp-message\",\n"
+" \"space\": \"dhcp4\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 23,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"01\",\n"
+" \"name\": \"default-ip-ttl\",\n"
+" \"space\": \"dhcp4\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 26
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 56,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"dhcp-message\",\n"
+" \"space\": \"dhcp4\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 23,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"01\",\n"
+" \"name\": \"default-ip-ttl\",\n"
+" \"space\": \"dhcp4\"\n"
+" }\n"
+" ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 27
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 56,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"dhcp-message\",\n"
+" \"space\": \"dhcp4\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 56,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"1234\",\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 56,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"uint32\"\n"
+" }\n"
+" ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 28
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 1,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"1234\",\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 2,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"192.168.2.1\",\n"
+" \"name\": \"foo2\",\n"
+" \"space\": \"isc\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 1,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"uint32\"\n"
+" },\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 2,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo2\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"ipv4-address\"\n"
+" }\n"
+" ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 29
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 222,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"11\",\n"
+" \"name\": \"base-option\",\n"
+" \"space\": \"dhcp4\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 1,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"1234\",\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 2,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"192.168.2.1\",\n"
+" \"name\": \"foo2\",\n"
+" \"space\": \"isc\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 222,\n"
+" \"encapsulate\": \"isc\",\n"
+" \"name\": \"base-option\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"dhcp4\",\n"
+" \"type\": \"uint8\"\n"
+" },\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 1,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"uint32\"\n"
+" },\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 2,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo2\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"ipv4-address\"\n"
+" }\n"
+" ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 3000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 3000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 3000\n"
+" }\n",
+ // CONFIGURATION 30
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 56,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"AB\",\n"
+" \"name\": \"dhcp-message\",\n"
+" \"space\": \"dhcp4\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 56,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"dhcp-message\",\n"
+" \"space\": \"dhcp4\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 23,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"01\",\n"
+" \"name\": \"default-ip-ttl\",\n"
+" \"space\": \"dhcp4\"\n"
+" }\n"
+" ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 31
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 56,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"0102030405060708090A\",\n"
+" \"name\": \"dhcp-message\",\n"
+" \"space\": \"dhcp4\"\n"
+" }\n"
+" ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 2,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 23,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"FF\",\n"
+" \"name\": \"default-ip-ttl\",\n"
+" \"space\": \"dhcp4\"\n"
+" }\n"
+" ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.3.101-192.0.3.150\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.3.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 32
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 56,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"dhcp-message\",\n"
+" \"space\": \"dhcp4\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 23,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"01\",\n"
+" \"name\": \"default-ip-ttl\",\n"
+" \"space\": \"dhcp4\"\n"
+" }\n"
+" ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 33
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 56,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"dhcp-message\",\n"
+" \"space\": \"dhcp4\"\n"
+" }\n"
+" ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" },\n"
+" {\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 23,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"01\",\n"
+" \"name\": \"default-ip-ttl\",\n"
+" \"space\": \"dhcp4\"\n"
+" }\n"
+" ],\n"
+" \"pool\": \"192.0.2.200-192.0.2.250\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 34
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 78,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"true, 10.0.0.3, 127.0.0.1\",\n"
+" \"name\": \"slp-directory-agent\",\n"
+" \"space\": \"dhcp4\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 79,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"false, \",\n"
+" \"name\": \"slp-service-scope\",\n"
+" \"space\": \"dhcp4\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 35
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 1,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"1234\",\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"vendor-encapsulated-options-space\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 2,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"192.168.2.1\",\n"
+" \"name\": \"foo2\",\n"
+" \"space\": \"vendor-encapsulated-options-space\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 1,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"vendor-encapsulated-options-space\",\n"
+" \"type\": \"uint32\"\n"
+" },\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 2,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo2\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"vendor-encapsulated-options-space\",\n"
+" \"type\": \"ipv4-address\"\n"
+" }\n"
+" ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 36
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 43,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"0104000004D20204C0A80201\",\n"
+" \"name\": \"vendor-encapsulated-options\",\n"
+" \"space\": \"dhcp4\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 1,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"1234\",\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"vendor-encapsulated-options-space\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 2,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"192.168.2.1\",\n"
+" \"name\": \"foo2\",\n"
+" \"space\": \"vendor-encapsulated-options-space\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 1,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"vendor-encapsulated-options-space\",\n"
+" \"type\": \"uint32\"\n"
+" },\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 2,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo2\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"vendor-encapsulated-options-space\",\n"
+" \"type\": \"ipv4-address\"\n"
+" }\n"
+" ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 3000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 3000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 3000\n"
+" }\n",
+ // CONFIGURATION 37
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 100,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"1234\",\n"
+" \"space\": \"vendor-1234\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 100,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"space\": \"vendor-4491\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.10\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 38
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\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"
+" \"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"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 39
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 40
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 41
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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\": \"192.168.2.2\",\n"
+" \"sender-port\": 778,\n"
+" \"server-ip\": \"192.168.2.1\",\n"
+" \"server-port\": 777\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 42
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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\": \"192.168.2.2\",\n"
+" \"sender-port\": 778,\n"
+" \"server-ip\": \"192.168.2.1\",\n"
+" \"server-port\": 777\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 43
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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\": \"192.168.2.2\",\n"
+" \"sender-port\": 778,\n"
+" \"server-ip\": \"192.168.2.1\",\n"
+" \"server-port\": 777\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 44
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4,\n"
+" \"min-valid-lifetime\": 4,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ \"192.0.2.123\" ]\n"
+" },\n"
+" \"renew-timer\": 1,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 45
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4,\n"
+" \"min-valid-lifetime\": 4,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ \"192.0.3.123\", \"192.0.3.124\" ]\n"
+" },\n"
+" \"renew-timer\": 1,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 46
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"client-class\": \"alpha\",\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"client-class\": \"beta\",\n"
+" \"id\": 2,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.3.101-192.0.3.150\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.3.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"client-class\": \"gamma\",\n"
+" \"id\": 3,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.4.101-192.0.4.150\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.4.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 4,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.5.101-192.0.5.150\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.5.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 47
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"client-class\": \"alpha\",\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" },\n"
+" {\n"
+" \"client-class\": \"beta\",\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.3.101-192.0.3.150\"\n"
+" },\n"
+" {\n"
+" \"client-class\": \"gamma\",\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.4.101-192.0.4.150\"\n"
+" },\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.5.101-192.0.5.150\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.0.0/16\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 48
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 123,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 234,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.3.101-192.0.3.150\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [\n"
+" {\n"
+" \"boot-file-name\": \"\",\n"
+" \"client-classes\": [ ],\n"
+" \"hostname\": \"\",\n"
+" \"hw-address\": \"01:02:03:04:05:06\",\n"
+" \"ip-address\": \"192.0.3.120\",\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 5,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"192.0.3.95\",\n"
+" \"name\": \"name-servers\",\n"
+" \"space\": \"dhcp4\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 23,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"11\",\n"
+" \"name\": \"default-ip-ttl\",\n"
+" \"space\": \"dhcp4\"\n"
+" }\n"
+" ],\n"
+" \"server-hostname\": \"\"\n"
+" },\n"
+" {\n"
+" \"boot-file-name\": \"\",\n"
+" \"client-classes\": [ ],\n"
+" \"duid\": \"01:02:03:04:05:06:07:08:09:0a\",\n"
+" \"hostname\": \"\",\n"
+" \"ip-address\": \"192.0.3.112\",\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 5,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"192.0.3.15\",\n"
+" \"name\": \"name-servers\",\n"
+" \"space\": \"dhcp4\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 23,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"32\",\n"
+" \"name\": \"default-ip-ttl\",\n"
+" \"space\": \"dhcp4\"\n"
+" }\n"
+" ],\n"
+" \"server-hostname\": \"\"\n"
+" }\n"
+" ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.3.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 542,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.4.101-192.0.4.150\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [\n"
+" {\n"
+" \"boot-file-name\": \"\",\n"
+" \"client-classes\": [ ],\n"
+" \"client-id\": \"05010203040506\",\n"
+" \"hostname\": \"\",\n"
+" \"ip-address\": \"192.0.4.103\",\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"server-hostname\": \"\"\n"
+" },\n"
+" {\n"
+" \"boot-file-name\": \"\",\n"
+" \"circuit-id\": \"060504030201\",\n"
+" \"client-classes\": [ ],\n"
+" \"hostname\": \"\",\n"
+" \"ip-address\": \"192.0.4.102\",\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"server-hostname\": \"\"\n"
+" },\n"
+" {\n"
+" \"boot-file-name\": \"\",\n"
+" \"client-classes\": [ ],\n"
+" \"duid\": \"0a:09:08:07:06:05:04:03:02:01\",\n"
+" \"hostname\": \"\",\n"
+" \"ip-address\": \"192.0.4.101\",\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 5,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"192.0.4.11\",\n"
+" \"name\": \"name-servers\",\n"
+" \"space\": \"dhcp4\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 23,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"95\",\n"
+" \"name\": \"default-ip-ttl\",\n"
+" \"space\": \"dhcp4\"\n"
+" }\n"
+" ],\n"
+" \"server-hostname\": \"\"\n"
+" }\n"
+" ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.4.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 49
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\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"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 234,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.3.101-192.0.3.150\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [\n"
+" {\n"
+" \"boot-file-name\": \"\",\n"
+" \"client-classes\": [ ],\n"
+" \"duid\": \"01:02:03:04:05:06:07:08:09:0a\",\n"
+" \"hostname\": \"\",\n"
+" \"ip-address\": \"192.0.3.112\",\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 100,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"123\",\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\"\n"
+" }\n"
+" ],\n"
+" \"server-hostname\": \"\"\n"
+" }\n"
+" ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.3.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 50
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.1.0/24\"\n"
+" }\n"
+" ],\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\": \"192.0.1.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 2,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\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\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 3,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.3.0/24\"\n"
+" }\n"
+" ],\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\": \"192.0.3.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 4,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.4.0/24\"\n"
+" }\n"
+" ],\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\": \"192.0.4.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 5,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.5.0/24\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.5.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 6,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.6.0/24\"\n"
+" }\n"
+" ],\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\": \"192.0.6.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 7,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.7.0/24\"\n"
+" }\n"
+" ],\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\": \"192.0.7.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 51
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.0/24\"\n"
+" }\n"
+" ],\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\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 2,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.3.0/24\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.3.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 52
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 53
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 54
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 55
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 56
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 57
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"2001:db8::123/45\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 58
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"ethX\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 59
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"ethX\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"2001:db8::543/21\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 60
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"vlan123\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 61
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"client-classes\": [\n"
+" {\n"
+" \"boot-file-name\": \"\",\n"
+" \"name\": \"one\",\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"server-hostname\": \"\"\n"
+" },\n"
+" {\n"
+" \"boot-file-name\": \"\",\n"
+" \"name\": \"two\",\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"server-hostname\": \"\"\n"
+" },\n"
+" {\n"
+" \"boot-file-name\": \"\",\n"
+" \"name\": \"three\",\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"server-hostname\": \"\"\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 62
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"client-classes\": [\n"
+" {\n"
+" \"boot-file-name\": \"\",\n"
+" \"max-valid-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 1000,\n"
+" \"name\": \"one\",\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"server-hostname\": \"\",\n"
+" \"valid-lifetime\": 2000\n"
+" },\n"
+" {\n"
+" \"boot-file-name\": \"\",\n"
+" \"name\": \"two\",\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"server-hostname\": \"\"\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 7200,\n"
+" \"min-valid-lifetime\": 7200,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 63
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.0/28\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 64
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.0/28\",\n"
+" \"user-context\": { }\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 65
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.0/28\",\n"
+" \"user-context\": {\n"
+" \"bool-param\": true,\n"
+" \"integer-param\": 42,\n"
+" \"string-param\": \"Sagittarius\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 66
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.0/28\",\n"
+" \"user-context\": {\n"
+" \"bool-param\": true,\n"
+" \"integer-param\": 42,\n"
+" \"string-param\": \"Sagittarius\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 67
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 68
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"client-classes\": [\n"
+" {\n"
+" \"boot-file-name\": \"\",\n"
+" \"name\": \"all\",\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"server-hostname\": \"\",\n"
+" \"test\": \"'' == ''\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"match all\"\n"
+" }\n"
+" },\n"
+" {\n"
+" \"boot-file-name\": \"\",\n"
+" \"name\": \"none\",\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"server-hostname\": \"\"\n"
+" },\n"
+" {\n"
+" \"boot-file-name\": \"\",\n"
+" \"name\": \"both\",\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"server-hostname\": \"\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"a comment\",\n"
+" \"version\": 1\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"control-socket\": {\n"
+" \"socket-name\": \"/tmp/kea4-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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 56,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"dhcp-message\",\n"
+" \"space\": \"dhcp4\",\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\": \"ipv4-address\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"An option definition\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [\n"
+" {\n"
+" \"calculate-tee-times\": false,\n"
+" \"max-valid-lifetime\": 7200,\n"
+" \"min-valid-lifetime\": 7200,\n"
+" \"name\": \"foo\",\n"
+" \"option-data\": [ ],\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 100,\n"
+" \"max-valid-lifetime\": 7200,\n"
+" \"min-valid-lifetime\": 7200,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.1.1-192.0.1.10\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"A pool\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"reservations\": [\n"
+" {\n"
+" \"boot-file-name\": \"\",\n"
+" \"client-classes\": [ ],\n"
+" \"hostname\": \"foo.example.com\",\n"
+" \"hw-address\": \"aa:bb:cc:dd:ee:ff\",\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 15,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"example.com\",\n"
+" \"name\": \"domain-name\",\n"
+" \"space\": \"dhcp4\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"An option in a reservation\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"server-hostname\": \"\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"A host reservation\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.1.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"user-context\": {\n"
+" \"comment\": \"A subnet\"\n"
+" },\n"
+" \"valid-lifetime\": 7200\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\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"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"user-context\": {\n"
+" \"comment\": \"A DHCPv4 server\"\n"
+" },\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 69
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [\n"
+" {\n"
+" \"boot-file-name\": \"\",\n"
+" \"client-classes\": [ ],\n"
+" \"hostname\": \"global2\",\n"
+" \"hw-address\": \"01:02:03:04:05:06\",\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 5,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"192.0.3.95\",\n"
+" \"name\": \"name-servers\",\n"
+" \"space\": \"dhcp4\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 23,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"11\",\n"
+" \"name\": \"default-ip-ttl\",\n"
+" \"space\": \"dhcp4\"\n"
+" }\n"
+" ],\n"
+" \"server-hostname\": \"\"\n"
+" },\n"
+" {\n"
+" \"boot-file-name\": \"\",\n"
+" \"client-classes\": [ ],\n"
+" \"duid\": \"01:02:03:04:05:06:07:08:09:0a\",\n"
+" \"hostname\": \"global1\",\n"
+" \"ip-address\": \"192.0.200.1\",\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 5,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"192.0.3.15\",\n"
+" \"name\": \"name-servers\",\n"
+" \"space\": \"dhcp4\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 23,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"32\",\n"
+" \"name\": \"default-ip-ttl\",\n"
+" \"space\": \"dhcp4\"\n"
+" }\n"
+" ],\n"
+" \"server-hostname\": \"\"\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 123,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 542,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.4.101-192.0.4.150\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.4.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 70
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [\n"
+" {\n"
+" \"calculate-tee-times\": true,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"name\": \"foo\",\n"
+" \"option-data\": [ ],\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 100,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.1.1-192.0.1.10\"\n"
+" }\n"
+" ],\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.1.0/24\",\n"
+" \"t1-percent\": 0.45,\n"
+" \"t2-percent\": 0.65,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 200,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.10\"\n"
+" }\n"
+" ],\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.4,\n"
+" \"t2-percent\": 0.75,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.4,\n"
+" \"t2-percent\": 0.75,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 300,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.3.0/28\"\n"
+" }\n"
+" ],\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.3.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 71
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": true,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 2,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.3.1-192.0.3.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.3.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 72
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": true,\n"
+" \"subnet4\": [\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"192.0.2.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"4o6-interface\": \"\",\n"
+" \"4o6-interface-id\": \"\",\n"
+" \"4o6-subnet\": \"\",\n"
+" \"calculate-tee-times\": false,\n"
+" \"id\": 2,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"192.0.3.1-192.0.3.100\"\n"
+" }\n"
+" ],\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": true,\n"
+" \"subnet\": \"192.0.3.0/24\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 73
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"rebind-timer\": 2000,\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"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 5,\n"
+" \"statistic-default-sample-count\": 10,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 74
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 75
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\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"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 1024,\n"
+" \"thread-pool-size\": 48\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 76
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false,\n"
+" \"service-sockets-require-all\": true\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 77
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false,\n"
+" \"service-sockets-max-retries\": 10,\n"
+" \"service-sockets-retry-wait-time\": 5000\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 78
+"{\n"
+" \"authoritative\": false,\n"
+" \"boot-file-name\": \"\",\n"
+" \"calculate-tee-times\": false,\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-ring4\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"echo-client-id\": true,\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\", \"circuit-id\", \"client-id\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false,\n"
+" \"service-sockets-max-retries\": 10,\n"
+" \"service-sockets-require-all\": true,\n"
+" \"service-sockets-retry-wait-time\": 1000\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"match-client-id\": true,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": false,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"next-server\": \"0.0.0.0\",\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-hostname\": \"\",\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet4\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.875,\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 = parseDHCP4(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 Dhcp4ParserTest)
+class Dhcp4GetConfigTest : public ::testing::TestWithParam<size_t> {
+public:
+ Dhcp4GetConfigTest()
+ : rcode_(-1) {
+ // 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 ControlledDhcpv4Srv(0));
+ // Create fresh context.
+ resetConfiguration();
+ }
+
+ ~Dhcp4GetConfigTest() {
+ 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 DHCP4 parser
+ try {
+ json = parseDHCP4(config, true);
+ } catch (...) {
+ ADD_FAILURE() << "parsing failed for " << operation
+ << " on\n" << prettyPrint(json) << "\n";
+ return (false);
+ }
+
+ // try DHCP4 configure
+ ConstElementPtr status;
+ try {
+ status = configureDhcp4Server(*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\": [ \"*\" ] },"
+ "\"valid-lifetime\": 4000, "
+ "\"subnet4\": [ ], "
+ "\"dhcp-ddns\": { \"enable-updates\" : false }, "
+ "\"option-def\": [ ], "
+ "\"option-data\": [ ] }";
+ EXPECT_TRUE(executeConfiguration(config, "reset configuration"));
+ CfgMgr::instance().clear();
+ CfgMgr::instance().setFamily(AF_INET);
+ }
+
+ boost::scoped_ptr<ControlledDhcpv4Srv> srv_; ///< DHCP4 server under test
+ int rcode_; ///< Return code from element parsing
+ ConstElementPtr comment_; ///< Reason for parse fail
+};
+
+/// Test a configuration
+TEST_P(Dhcp4GetConfigTest, 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(unparsed = extracted->toElement());
+ ConstElementPtr dhcp;
+ ASSERT_NO_THROW(dhcp = unparsed->get("Dhcp4"));
+ 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 dhcpv4 syntax parser
+ ElementPtr jsond;
+ ASSERT_NO_THROW_LOG(jsond = parseDHCP4(expected, true));
+ ElementPtr jsonj;
+ // get the expected config using the generic JSON syntax parser
+ 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(Dhcp4GetConfigTest, Dhcp4GetConfigTest,
+ ::testing::Range(static_cast<size_t>(0),
+ max_config_counter),
+ IntToString());
+#else
+INSTANTIATE_TEST_CASE_P(Dhcp4GetConfigTest, Dhcp4GetConfigTest,
+ ::testing::Range(static_cast<size_t>(0),
+ max_config_counter),
+ IntToString());
+#endif
+} // namespace
diff --git a/src/bin/dhcp4/tests/get_config_unittest.cc.skel b/src/bin/dhcp4/tests/get_config_unittest.cc.skel
new file mode 100644
index 0000000..a25a5ab
--- /dev/null
+++ b/src/bin/dhcp4/tests/get_config_unittest.cc.skel
@@ -0,0 +1,374 @@
+// 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_parser4.h>
+#include <dhcp4/dhcp4_srv.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/json_config_parser.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp4/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 dhcp4_unittests on Dhcp4ParserTest
+/// redirecting the standard error to a temporary file, e.g. by
+/// @code
+/// ./dhcp4_unittests --gtest_filter="Dhcp4Parser*" > /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 dhcp4_unittests on Dhcp4GetConfigTest
+/// redirecting the standard error to a temporary file, e.g. by
+/// @code
+/// ./dhcp4_unittests --gtest_filter="Dhcp4GetConfig*" > /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 = parseDHCP4(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 Dhcp4ParserTest)
+class Dhcp4GetConfigTest : public ::testing::TestWithParam<size_t> {
+public:
+ Dhcp4GetConfigTest()
+ : rcode_(-1) {
+ // 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 ControlledDhcpv4Srv(0));
+ // Create fresh context.
+ resetConfiguration();
+ }
+
+ ~Dhcp4GetConfigTest() {
+ 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 DHCP4 parser
+ try {
+ json = parseDHCP4(config, true);
+ } catch (...) {
+ ADD_FAILURE() << "parsing failed for " << operation
+ << " on\n" << prettyPrint(json) << "\n";
+ return (false);
+ }
+
+ // try DHCP4 configure
+ ConstElementPtr status;
+ try {
+ status = configureDhcp4Server(*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\": [ \"*\" ] },"
+ "\"valid-lifetime\": 4000, "
+ "\"subnet4\": [ ], "
+ "\"dhcp-ddns\": { \"enable-updates\" : false }, "
+ "\"option-def\": [ ], "
+ "\"option-data\": [ ] }";
+ EXPECT_TRUE(executeConfiguration(config, "reset configuration"));
+ CfgMgr::instance().clear();
+ CfgMgr::instance().setFamily(AF_INET);
+ }
+
+ boost::scoped_ptr<ControlledDhcpv4Srv> srv_; ///< DHCP4 server under test
+ int rcode_; ///< Return code from element parsing
+ ConstElementPtr comment_; ///< Reason for parse fail
+};
+
+/// Test a configuration
+TEST_P(Dhcp4GetConfigTest, 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(unparsed = extracted->toElement());
+ ConstElementPtr dhcp;
+ ASSERT_NO_THROW(dhcp = unparsed->get("Dhcp4"));
+ 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 dhcpv4 syntax parser
+ ElementPtr jsond;
+ ASSERT_NO_THROW_LOG(jsond = parseDHCP4(expected, true));
+ ElementPtr jsonj;
+ // get the expected config using the generic JSON syntax parser
+ 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(Dhcp4GetConfigTest, Dhcp4GetConfigTest,
+ ::testing::Range(static_cast<size_t>(0),
+ max_config_counter),
+ IntToString());
+#else
+INSTANTIATE_TEST_CASE_P(Dhcp4GetConfigTest, Dhcp4GetConfigTest,
+ ::testing::Range(static_cast<size_t>(0),
+ max_config_counter),
+ IntToString());
+#endif
+} // namespace
diff --git a/src/bin/dhcp4/tests/get_config_unittest.h b/src/bin/dhcp4/tests/get_config_unittest.h
new file mode 100644
index 0000000..e1718e0
--- /dev/null
+++ b/src/bin/dhcp4/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/dhcp4/tests/hooks_unittest.cc b/src/bin/dhcp4/tests/hooks_unittest.cc
new file mode 100644
index 0000000..049a065
--- /dev/null
+++ b/src/bin/dhcp4/tests/hooks_unittest.cc
@@ -0,0 +1,3120 @@
+// Copyright (C) 2015-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 <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/json_config_parser.h>
+#include <asiolink/io_service.h>
+#include <cc/command_interpreter.h>
+#include <config/command_mgr.h>
+#include <hooks/server_hooks.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/callout_manager.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp/option.h>
+#include <asiolink/io_address.h>
+#include <dhcp4/tests/dhcp4_client.h>
+#include <dhcp4/tests/marker_file.h>
+#include <dhcp4/tests/test_libraries.h>
+#include <stats/stats_mgr.h>
+#include <util/multi_threading_mgr.h>
+
+#include <vector>
+
+using namespace std;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::hooks;
+using namespace isc::config;
+using namespace isc::dhcp::test;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace isc::stats;
+
+// Checks if hooks are registered properly.
+TEST_F(Dhcpv4SrvTest, Hooks) {
+ NakedDhcpv4Srv srv(0);
+
+ // check if appropriate hooks are registered
+ int hook_index_buffer4_receive = -1;
+ int hook_index_pkt4_receive = -1;
+ int hook_index_select_subnet = -1;
+ int hook_index_leases4_committed = -1;
+ int hook_index_lease4_release = -1;
+ int hook_index_pkt4_send = -1;
+ int hook_index_buffer4_send = -1;
+ int hook_index_host4_identifier = -1;
+
+ // check if appropriate indexes are set
+ EXPECT_NO_THROW(hook_index_buffer4_receive = ServerHooks::getServerHooks()
+ .getIndex("buffer4_receive"));
+ EXPECT_NO_THROW(hook_index_pkt4_receive = ServerHooks::getServerHooks()
+ .getIndex("pkt4_receive"));
+ EXPECT_NO_THROW(hook_index_select_subnet = ServerHooks::getServerHooks()
+ .getIndex("subnet4_select"));
+ EXPECT_NO_THROW(hook_index_leases4_committed = ServerHooks::getServerHooks()
+ .getIndex("leases4_committed"));
+ EXPECT_NO_THROW(hook_index_lease4_release = ServerHooks::getServerHooks()
+ .getIndex("lease4_release"));
+ EXPECT_NO_THROW(hook_index_pkt4_send = ServerHooks::getServerHooks()
+ .getIndex("pkt4_send"));
+ EXPECT_NO_THROW(hook_index_buffer4_send = ServerHooks::getServerHooks()
+ .getIndex("buffer4_send"));
+ EXPECT_NO_THROW(hook_index_host4_identifier = ServerHooks::getServerHooks()
+ .getIndex("host4_identifier"));
+
+ EXPECT_TRUE(hook_index_buffer4_receive > 0);
+ EXPECT_TRUE(hook_index_pkt4_receive > 0);
+ EXPECT_TRUE(hook_index_select_subnet > 0);
+ EXPECT_TRUE(hook_index_leases4_committed > 0);
+ EXPECT_TRUE(hook_index_lease4_release > 0);
+ EXPECT_TRUE(hook_index_pkt4_send > 0);
+ EXPECT_TRUE(hook_index_buffer4_send > 0);
+ EXPECT_TRUE(hook_index_host4_identifier > 0);
+}
+
+// A dummy MAC address, padded with 0s
+const uint8_t dummyChaddr[16] = {0, 1, 2, 3, 4, 5, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0 };
+
+// Let's use some creative test content here (128 chars + \0)
+const uint8_t dummyFile[] = "Lorem ipsum dolor sit amet, consectetur "
+ "adipiscing elit. Proin mollis placerat metus, at "
+ "lacinia orci ornare vitae. Mauris amet.";
+
+// Yet another type of test content (64 chars + \0)
+const uint8_t dummySname[] = "Lorem ipsum dolor sit amet, consectetur "
+ "adipiscing elit posuere.";
+
+/// @brief a class dedicated to Hooks testing in DHCPv4 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 HooksDhcpv4SrvTest : public Dhcpv4SrvTest {
+
+public:
+
+ /// @brief creates Dhcpv4Srv and prepares buffers for callouts
+ HooksDhcpv4SrvTest() {
+ HooksManager::setTestMode(false);
+ bool status = HooksManager::unloadLibraries();
+ if (!status) {
+ cerr << "(fixture ctor) unloadLibraries failed" << endl;
+ }
+
+ // Allocate new DHCPv6 Server
+ srv_ = new NakedDhcpv4Srv(0);
+
+ // clear static buffers
+ resetCalloutBuffers();
+
+ io_service_ = boost::make_shared<IOService>();
+
+ // Clear statistics.
+ StatsMgr::instance().removeAll();
+ }
+
+ /// @brief destructor (deletes Dhcpv4Srv)
+ virtual ~HooksDhcpv4SrvTest() {
+ // clear static buffers
+ resetCalloutBuffers();
+
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("dhcp4_srv_configured");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("buffer4_receive");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("buffer4_send");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("pkt4_receive");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("pkt4_send");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("subnet4_select");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("lease4_renew");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("lease4_release");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("lease4_decline");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("host4_identifier");
+
+ delete srv_;
+ 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 HooksDhcpv4SrvTest object
+ ///
+ /// @param option_code code of option to be created
+ ///
+ /// @return pointer to create option object
+ static OptionPtr createOption(uint16_t option_code) {
+
+ char payload[] = {
+ 0xa, 0xb, 0xc, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14
+ };
+
+ OptionBuffer tmp(payload, payload + sizeof(payload));
+ return OptionPtr(new Option(Option::V4, option_code, tmp));
+ }
+
+ /// @brief Generates test packet.
+ ///
+ /// Allocates and generates on-wire buffer that represents test packet, with all
+ /// fixed fields set to non-zero values. Content is not always reasonable.
+ ///
+ /// See generateTestPacket1() function that returns exactly the same packet as
+ /// Pkt4 object.
+ ///
+ /// @return pointer to allocated Pkt4 object
+ // Returns a vector containing a DHCPv4 packet header.
+ Pkt4Ptr
+ generateSimpleDiscover() {
+
+ // That is only part of the header. It contains all "short" fields,
+ // larger fields are constructed separately.
+ uint8_t hdr[] = {
+ 1, 6, 6, 13, // op, htype, hlen, hops,
+ 0x12, 0x34, 0x56, 0x78, // transaction-id
+ 0, 42, 0x80, 0x00, // 42 secs, BROADCAST flags
+ 192, 0, 2, 1, // ciaddr
+ 1, 2, 3, 4, // yiaddr
+ 192, 0, 2, 255, // siaddr
+ 192, 0, 2, 50, // giaddr
+ };
+
+ // Initialize the vector with the header fields defined above.
+ vector<uint8_t> buf(hdr, hdr + sizeof(hdr));
+
+ // Append the large header fields.
+ copy(dummyChaddr, dummyChaddr + Pkt4::MAX_CHADDR_LEN, back_inserter(buf));
+ copy(dummySname, dummySname + Pkt4::MAX_SNAME_LEN, back_inserter(buf));
+ copy(dummyFile, dummyFile + Pkt4::MAX_FILE_LEN, back_inserter(buf));
+
+ // Should now have all the header, so check. The "static_cast" is used
+ // to get round an odd bug whereby the linker appears not to find the
+ // definition of DHCPV4_PKT_HDR_LEN if it appears within an EXPECT_EQ().
+ EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), buf.size());
+
+ // Add magic cookie
+ buf.push_back(0x63);
+ buf.push_back(0x82);
+ buf.push_back(0x53);
+ buf.push_back(0x63);
+
+ // Add message type DISCOVER
+ buf.push_back(static_cast<uint8_t>(DHO_DHCP_MESSAGE_TYPE));
+ buf.push_back(1); // length (just one byte)
+ buf.push_back(static_cast<uint8_t>(DHCPDISCOVER));
+
+ Pkt4Ptr dis(new Pkt4(&buf[0], buf.size()));
+ // Interface must be selected for a Discover. Server will use the interface
+ // name to select a subnet for a client. This test is using fake interfaces
+ // and the fake eth0 interface has IPv4 address matching the subnet
+ // currently configured for this test.
+ dis->setIface("eth1");
+ dis->setIndex(ETH1_INDEX);
+ return (dis);
+ }
+
+ /// @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 Pkt4Ptr& 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());
+ }
+
+ /// Test callback that stores received callout name and pkt4 value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer4_receive_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("buffer4_receive");
+
+ callout_handle.getArgument("query4", callback_qry_pkt4_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+
+ if (callback_qry_pkt4_) {
+ callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions();
+ }
+ return (0);
+ }
+
+ /// Test callback that changes hwaddr value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer4_receive_change_hwaddr(CalloutHandle& callout_handle) {
+
+ Pkt4Ptr pkt;
+ callout_handle.getArgument("query4", pkt);
+
+ // If there is at least one option with data
+ if (pkt->data_.size() >= Pkt4::DHCPV4_PKT_HDR_LEN) {
+ // Offset of the first byte of the CHADDR field. Let's the first
+ // byte to some new value that we could later check
+ pkt->data_[28] = 0xff;
+ }
+
+ // Carry on as usual
+ return buffer4_receive_callout(callout_handle);
+ }
+
+ /// Test callback that sets drop flag
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer4_receive_drop(CalloutHandle& callout_handle) {
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+
+ // Carry on as usual
+ return buffer4_receive_callout(callout_handle);
+ }
+
+ /// Test callback that sets skip flag
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer4_receive_skip(CalloutHandle& callout_handle) {
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ // Carry on as usual
+ return buffer4_receive_callout(callout_handle);
+ }
+
+ /// test callback that stores received callout name and pkt4 value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt4_receive_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("pkt4_receive");
+
+ callout_handle.getArgument("query4", callback_qry_pkt4_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+
+ if (callback_qry_pkt4_) {
+ callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ /// test callback that changes client-id value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt4_receive_change_clientid(CalloutHandle& callout_handle) {
+
+ Pkt4Ptr pkt;
+ callout_handle.getArgument("query4", pkt);
+
+ // get rid of the old client-id
+ pkt->delOption(DHO_DHCP_CLIENT_IDENTIFIER);
+
+ // add a new option
+ pkt->addOption(createOption(DHO_DHCP_CLIENT_IDENTIFIER));
+
+ // carry on as usual
+ return pkt4_receive_callout(callout_handle);
+ }
+
+ /// test callback that deletes client-id
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt4_receive_delete_clientid(CalloutHandle& callout_handle) {
+
+ Pkt4Ptr pkt;
+ callout_handle.getArgument("query4", pkt);
+
+ // get rid of the old client-id (and no HWADDR)
+ vector<uint8_t> mac;
+ pkt->delOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ pkt->setHWAddr(1, 0, mac); // HWtype 1, hardware len = 0
+
+ // carry on as usual
+ return pkt4_receive_callout(callout_handle);
+ }
+
+ /// test callback that sets drop flag
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt4_receive_drop(CalloutHandle& callout_handle) {
+
+ Pkt4Ptr pkt;
+ callout_handle.getArgument("query4", pkt);
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+
+ // carry on as usual
+ return pkt4_receive_callout(callout_handle);
+ }
+
+ /// test callback that sets skip flag
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt4_receive_skip(CalloutHandle& callout_handle) {
+
+ Pkt4Ptr pkt;
+ callout_handle.getArgument("query4", pkt);
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ // carry on as usual
+ return pkt4_receive_callout(callout_handle);
+ }
+
+ /// Test callback that stores received callout name and pkt4 value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt4_send_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("pkt4_send");
+
+ callout_handle.getArgument("response4", callback_resp_pkt4_);
+
+ callout_handle.getArgument("query4", callback_qry_pkt4_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+
+ if (callback_qry_pkt4_) {
+ callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions();
+ }
+
+ if (callback_resp_pkt4_) {
+ callback_resp_options_copy_ = callback_resp_pkt4_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ // Test callback that changes server-id
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt4_send_change_serverid(CalloutHandle& callout_handle) {
+
+ Pkt4Ptr pkt;
+ callout_handle.getArgument("response4", pkt);
+
+ // get rid of the old server-id
+ pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER);
+
+ // add a new option
+ pkt->addOption(createOption(DHO_DHCP_SERVER_IDENTIFIER));
+
+ // carry on as usual
+ return pkt4_send_callout(callout_handle);
+ }
+
+ /// test callback that deletes server-id
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt4_send_delete_serverid(CalloutHandle& callout_handle) {
+
+ Pkt4Ptr pkt;
+ callout_handle.getArgument("response4", pkt);
+
+ // get rid of the old client-id
+ pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER);
+
+ // carry on as usual
+ return pkt4_send_callout(callout_handle);
+ }
+
+ /// Test callback that sets skip flag
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt4_send_skip(CalloutHandle& callout_handle) {
+
+ Pkt4Ptr pkt;
+ callout_handle.getArgument("response4", pkt);
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ // carry on as usual
+ return pkt4_send_callout(callout_handle);
+ }
+
+ /// Test callback that sets drop flag
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt4_send_drop(CalloutHandle& callout_handle) {
+
+ Pkt4Ptr pkt;
+ callout_handle.getArgument("response4", pkt);
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+
+ // carry on as usual
+ return pkt4_send_callout(callout_handle);
+ }
+
+ /// Test callback that stores received callout name and pkt4 value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer4_send_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("buffer4_send");
+
+ callout_handle.getArgument("response4", callback_resp_pkt4_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+
+ if (callback_resp_pkt4_) {
+ callback_resp_options_copy_ = callback_resp_pkt4_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ /// Test callback changes the output buffer to a hardcoded value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer4_send_change_callout(CalloutHandle& callout_handle) {
+
+ Pkt4Ptr pkt;
+ callout_handle.getArgument("response4", pkt);
+
+ // modify buffer to set a different payload
+ pkt->getBuffer().clear();
+ pkt->getBuffer().writeData(dummyFile, sizeof(dummyFile));
+
+ return (0);
+ }
+
+ /// Test callback that stores received callout name and pkt4 value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ skip_callout(CalloutHandle& callout_handle) {
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ return (0);
+ }
+
+ /// Test callback that stores received callout name and pkt4 value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ drop_callout(CalloutHandle& callout_handle) {
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+
+ return (0);
+ }
+
+ /// Test callback that stores received callout name and subnet4 values
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ subnet4_select_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("subnet4_select");
+
+ callout_handle.getArgument("query4", callback_qry_pkt4_);
+ callout_handle.getArgument("subnet4", callback_subnet4_);
+ callout_handle.getArgument("subnet4collection", callback_subnet4collection_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+
+ if (callback_qry_pkt4_) {
+ callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ /// Test callback that picks the other subnet if possible.
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ subnet4_select_different_subnet_callout(CalloutHandle& callout_handle) {
+
+ // Call the basic callout to record all passed values
+ subnet4_select_callout(callout_handle);
+
+ const Subnet4Collection* subnets;
+ Subnet4Ptr subnet;
+ callout_handle.getArgument("subnet4", subnet);
+ callout_handle.getArgument("subnet4collection", 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("subnet4", subnet);
+ }
+
+ return (0);
+ }
+
+ /// Test callback that sets drop flag
+ static int
+ subnet4_select_drop_callout(CalloutHandle& callout_handle) {
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+
+ // Carry on as usual
+ return subnet4_select_callout(callout_handle);
+ }
+
+ /// Test callback that stores received callout name passed parameters
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease4_release_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("lease4_release");
+
+ callout_handle.getArgument("query4", callback_qry_pkt4_);
+ callout_handle.getArgument("lease4", callback_lease4_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+
+ if (callback_qry_pkt4_) {
+ callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ /// Test callback that stores received callout name and subnet4 values
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease4_renew_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("lease4_renew");
+
+ callout_handle.getArgument("query4", callback_qry_pkt4_);
+ callout_handle.getArgument("subnet4", callback_subnet4_);
+ callout_handle.getArgument("lease4", callback_lease4_);
+ callout_handle.getArgument("hwaddr", callback_hwaddr_);
+ callout_handle.getArgument("clientid", callback_clientid_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+
+ if (callback_qry_pkt4_) {
+ callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ /// Test lease4_decline callback that stores received parameters.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease4_decline_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("lease4_decline");
+ callout_handle.getArgument("query4", callback_qry_pkt4_);
+ callout_handle.getArgument("lease4", callback_lease4_);
+
+ if (callback_qry_pkt4_) {
+ callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ /// Test lease4_decline callback that sets next step to SKIP.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease4_decline_skip_callout(CalloutHandle& callout_handle) {
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ return (lease4_decline_callout(callout_handle));
+ }
+
+ /// Test lease4_decline callback that sets next step to DROP.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease4_decline_drop_callout(CalloutHandle& callout_handle) {
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+
+ return (lease4_decline_callout(callout_handle));
+ }
+
+ /// Test callback that stores values passed to leases4_committed.
+ static int
+ leases4_committed_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("leases4_committed");
+ callout_handle.getArgument("query4", callback_qry_pkt4_);
+
+ Lease4CollectionPtr leases4;
+ callout_handle.getArgument("leases4", leases4);
+ if (leases4->size() > 0) {
+ callback_lease4_ = leases4->at(0);
+ }
+
+ Lease4CollectionPtr deleted_leases4;
+ callout_handle.getArgument("deleted_leases4", deleted_leases4);
+ if (deleted_leases4->size() > 0) {
+ callback_deleted_lease4_ = deleted_leases4->at(0);
+ }
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ sort(callback_argument_names_.begin(), callback_argument_names_.end());
+
+ if (callback_qry_pkt4_) {
+ callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ static void
+ leases4_committed_unpark(ParkingLotHandlePtr parking_lot, Pkt4Ptr query) {
+ parking_lot->unpark(query);
+ }
+
+ /// Test callback which asks the server to park the packet.
+ static int
+ leases4_committed_park_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("leases4_committed");
+
+ callout_handle.getArgument("query4", callback_qry_pkt4_);
+
+ io_service_->post(std::bind(&HooksDhcpv4SrvTest::leases4_committed_unpark,
+ callout_handle.getParkingLotHandlePtr(),
+ callback_qry_pkt4_));
+
+ callout_handle.getParkingLotHandlePtr()->reference(callback_qry_pkt4_);
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_PARK);
+
+ Lease4CollectionPtr leases4;
+ callout_handle.getArgument("leases4", leases4);
+ if (leases4->size() > 0) {
+ callback_lease4_ = leases4->at(0);
+ }
+
+ Lease4CollectionPtr deleted_leases4;
+ callout_handle.getArgument("deleted_leases4", deleted_leases4);
+ if (deleted_leases4->size() > 0) {
+ callback_deleted_lease4_ = deleted_leases4->at(0);
+ }
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ sort(callback_argument_names_.begin(), callback_argument_names_.end());
+
+ if (callback_qry_pkt4_) {
+ callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ /// @brief Test host4_identifier callout by setting identifier to "foo"
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ host4_identifier_foo_callout(CalloutHandle& handle) {
+ callback_name_ = string("host4_identifier");
+
+ // Make sure the query4 parameter is passed.
+ handle.getArgument("query4", callback_qry_pkt4_);
+
+ // 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);
+
+ 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 host4_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
+ host4_identifier_hwaddr_callout(CalloutHandle& handle) {
+ callback_name_ = string("host4_identifier");
+
+ // Make sure the query4 parameter is passed.
+ handle.getArgument("query4", callback_qry_pkt4_);
+
+ // 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_pkt4_.reset();
+ callback_qry_pkt4_.reset();
+ callback_lease4_.reset();
+ callback_deleted_lease4_.reset();
+ callback_hwaddr_.reset();
+ callback_clientid_.reset();
+ callback_subnet4_.reset();
+ callback_subnet4collection_ = 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 Dhcpv4Srv that is used in tests
+ NakedDhcpv4Srv* srv_;
+
+ /// Pointer to the IO service used in the tests.
+ static IOServicePtr io_service_;
+
+ // The following fields are used in testing pkt4_receive_callout
+
+ /// String name of the received callout
+ static string callback_name_;
+
+ /// Client/query Pkt4 structure returned in the callout
+ static Pkt4Ptr callback_qry_pkt4_;
+
+ /// Server/response Pkt4 structure returned in the callout
+ static Pkt4Ptr callback_resp_pkt4_;
+
+ /// Lease4 structure returned in the leases4_committed callout
+ static Lease4Ptr callback_lease4_;
+
+ /// Lease4 structure returned in the leases4_committed callout
+ static Lease4Ptr callback_deleted_lease4_;
+
+ /// Hardware address returned in the callout
+ static HWAddrPtr callback_hwaddr_;
+
+ /// Client-id returned in the callout
+ static ClientIdPtr callback_clientid_;
+
+ /// Pointer to a subnet received by callout
+ static Subnet4Ptr callback_subnet4_;
+
+ /// A list of all available subnets (received by callout)
+ static const Subnet4Collection* callback_subnet4collection_;
+
+ /// 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 fields are used in testing pkt4_receive_callout.
+// See fields description in the class for details
+IOServicePtr HooksDhcpv4SrvTest::io_service_;
+string HooksDhcpv4SrvTest::callback_name_;
+Pkt4Ptr HooksDhcpv4SrvTest::callback_qry_pkt4_;
+Pkt4Ptr HooksDhcpv4SrvTest::callback_resp_pkt4_;
+Subnet4Ptr HooksDhcpv4SrvTest::callback_subnet4_;
+HWAddrPtr HooksDhcpv4SrvTest::callback_hwaddr_;
+ClientIdPtr HooksDhcpv4SrvTest::callback_clientid_;
+Lease4Ptr HooksDhcpv4SrvTest::callback_lease4_;
+Lease4Ptr HooksDhcpv4SrvTest::callback_deleted_lease4_;
+const Subnet4Collection* HooksDhcpv4SrvTest::callback_subnet4collection_;
+vector<string> HooksDhcpv4SrvTest::callback_argument_names_;
+bool HooksDhcpv4SrvTest::callback_qry_options_copy_;
+bool HooksDhcpv4SrvTest::callback_resp_options_copy_;
+
+/// @brief Fixture class used to do basic library load/unload tests
+class LoadUnloadDhcpv4SrvTest : public ::testing::Test {
+public:
+ /// @brief Pointer to the tested server object
+ boost::shared_ptr<NakedDhcpv4Srv> server_;
+
+ LoadUnloadDhcpv4SrvTest() {
+ reset();
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Destructor
+ ~LoadUnloadDhcpv4SrvTest() {
+ 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.
+ 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 pkt4_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 "buffer4_receive".
+TEST_F(HooksDhcpv4SrvTest, Buffer4ReceiveSimple) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer4_receive", buffer4_receive_callout));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr dis = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(dis);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered buffer4_receive callback.
+ srv_->run();
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("buffer4_receive", callback_name_);
+
+ // Check that pkt4 argument passing was successful and returned proper value
+ EXPECT_TRUE(callback_qry_pkt4_.get() == dis.get());
+
+ // Check that all expected parameters are there
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back(string("query4"));
+
+ 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(dis);
+}
+
+// Checks if callouts installed on buffer4_receive is able to change
+// the values and the parameters are indeed used by the server.
+TEST_F(HooksDhcpv4SrvTest, buffer4ReceiveValueChange) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Install callback that modifies MAC addr of incoming packet
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer4_receive", buffer4_receive_change_hwaddr));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr discover = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(discover);
+
+ // 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 buffer4_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
+ Pkt4Ptr offer = srv_->fake_sent_.front();
+ ASSERT_TRUE(offer);
+
+ // Get client-id...
+ HWAddrPtr hwaddr = offer->getHWAddr();
+
+ ASSERT_TRUE(hwaddr); // basic sanity check. HWAddr is always present
+
+ // ... and check if it is the modified value
+ ASSERT_FALSE(hwaddr->hwaddr_.empty()); // there must be a MAC address
+ EXPECT_EQ(0xff, hwaddr->hwaddr_[0]); // check that its first byte was modified
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(discover);
+}
+
+// Checks if callouts installed on buffer4_receive is able to set skip flag that
+// will cause the server to not parse the packet. Even though the packet is valid,
+// the server should eventually drop it, because there won't be mandatory options
+// (or rather option objects) in it.
+TEST_F(HooksDhcpv4SrvTest, buffer4ReceiveSkip) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer4_receive", buffer4_receive_skip));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr discover = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(discover);
+
+ // 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 pkt4_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(discover);
+}
+
+// Checks if callouts installed on buffer4_receive is able to set drop flag that
+// will cause the server to drop the packet.
+TEST_F(HooksDhcpv4SrvTest, buffer4ReceiveDrop) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer4_receive", buffer4_receive_drop));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr discover = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(discover);
+
+ // 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 pkt4_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(discover);
+}
+
+// Checks if callouts installed on pkt4_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 "pkt4_receive".
+TEST_F(HooksDhcpv4SrvTest, pkt4ReceiveSimple) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt4_receive", pkt4_receive_callout));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr sol = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_receive callback.
+ srv_->run();
+
+ // check that the callback called is indeed the one we installed
+ EXPECT_EQ("pkt4_receive", callback_name_);
+
+ // check that pkt4 argument passing was successful and returned proper value
+ EXPECT_TRUE(callback_qry_pkt4_.get() == sol.get());
+
+ // Check that all expected parameters are there
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back(string("query4"));
+
+ 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 pkt4_received is able to change
+// the values and the parameters are indeed used by the server.
+TEST_F(HooksDhcpv4SrvTest, valueChange_pkt4_receive) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt4_receive", pkt4_receive_change_clientid));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr sol = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_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
+ Pkt4Ptr adv = srv_->fake_sent_.front();
+ ASSERT_TRUE(adv);
+
+ // Get client-id...
+ OptionPtr clientid = adv->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+
+ // ... and check if it is the modified value
+ OptionPtr expected = createOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ EXPECT_TRUE(clientid->equals(expected));
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callouts installed on pkt4_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(HooksDhcpv4SrvTest, pkt4ReceiveDeleteClientId) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt4_receive", pkt4_receive_delete_clientid));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr sol = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_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 pkt4_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(HooksDhcpv4SrvTest, pkt4ReceiveSkip) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt4_receive", pkt4_receive_skip));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr sol = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_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 pkt4_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(HooksDhcpv4SrvTest, pkt4ReceiveDrop) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt4_receive", pkt4_receive_drop));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr sol = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_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 pkt4_send are indeed called and the
+// all necessary parameters are passed.
+TEST_F(HooksDhcpv4SrvTest, pkt4SendSimple) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt4_send", pkt4_send_callout));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr sol = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_receive callback.
+ srv_->run();
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("pkt4_send", callback_name_);
+
+ // Check that there is one packet sent
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+ Pkt4Ptr adv = srv_->fake_sent_.front();
+
+ // Check that pkt4 argument passing was successful and returned proper value
+ ASSERT_TRUE(callback_resp_pkt4_);
+ EXPECT_TRUE(callback_resp_pkt4_.get() == adv.get());
+
+ // That that the query4 argument was correctly set to the Discover we sent.
+ ASSERT_TRUE(callback_qry_pkt4_);
+ EXPECT_TRUE(callback_qry_pkt4_.get() == sol.get());
+
+ // Check that all expected parameters are there
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back(string("response4"));
+ expected_argument_names.push_back(string("query4"));
+ sort(callback_argument_names_.begin(), callback_argument_names_.end());
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ 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 pkt4_send is able to change
+// the values and the packet sent contains those changes
+TEST_F(HooksDhcpv4SrvTest, pkt4SendValueChange) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt4_send", pkt4_send_change_serverid));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr sol = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_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
+ Pkt4Ptr adv = srv_->fake_sent_.front();
+ ASSERT_TRUE(adv);
+
+ // Get client-id...
+ OptionPtr clientid = adv->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+
+ // ... and check if it is the modified value
+ OptionPtr expected = createOption(DHO_DHCP_SERVER_IDENTIFIER);
+ EXPECT_TRUE(clientid->equals(expected));
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callouts installed on pkt4_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(HooksDhcpv4SrvTest, pkt4SendDeleteServerId) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt4_send", pkt4_send_delete_serverid));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr sol = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_receive callback.
+ srv_->run();
+
+ // Check that the server indeed sent a malformed ADVERTISE
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+
+ // Get that ADVERTISE
+ Pkt4Ptr adv = srv_->fake_sent_.front();
+ ASSERT_TRUE(adv);
+
+ // Make sure that it does not have server-id
+ EXPECT_FALSE(adv->getOption(DHO_DHCP_SERVER_IDENTIFIER));
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callouts installed on pkt4_skip is able to set skip flag that
+// will cause the server to not process the packet (drop), even though it is valid.
+TEST_F(HooksDhcpv4SrvTest, skip_pkt4_send) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt4_send", pkt4_send_skip));
+
+ // Let's create a simple REQUEST
+ Pkt4Ptr sol = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_send callback.
+ srv_->run();
+
+ // Check that the server sent the message
+ 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)
+ Pkt4Ptr sent = srv_->fake_sent_.front();
+ EXPECT_EQ(0, sent->getBuffer().getLength());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callouts installed on pkt4_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(HooksDhcpv4SrvTest, drop_pkt4_send) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt4_send", pkt4_send_drop));
+
+ // Let's create a simple REQUEST
+ Pkt4Ptr sol = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_send callback.
+ srv_->run();
+
+ // Check that the server did not the message
+ 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 buffer4_send are indeed called and the
+// all necessary parameters are passed.
+TEST_F(HooksDhcpv4SrvTest, buffer4SendSimple) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer4_send", buffer4_send_callout));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr discover = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(discover);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_receive callback.
+ srv_->run();
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("buffer4_send", callback_name_);
+
+ // Check that there is one packet sent
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+ Pkt4Ptr adv = srv_->fake_sent_.front();
+
+ // Check that pkt4 argument passing was successful and returned proper value
+ EXPECT_TRUE(callback_resp_pkt4_.get() == adv.get());
+
+ // Check that all expected parameters are there
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back(string("response4"));
+ 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(discover);
+}
+
+// Checks if callouts installed on buffer4_send are indeed called and that
+// the output buffer can be changed.
+TEST_F(HooksDhcpv4SrvTest, buffer4Send) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer4_send", buffer4_send_change_callout));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr discover = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(discover);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_receive callback.
+ srv_->run();
+
+ // Check that there is one packet sent
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+ Pkt4Ptr adv = srv_->fake_sent_.front();
+
+ // The callout is supposed to fill the output buffer with dummyFile content
+ ASSERT_EQ(sizeof(dummyFile), adv->getBuffer().getLength());
+ EXPECT_EQ(0, memcmp(adv->getBuffer().getData(), dummyFile, sizeof(dummyFile)));
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(discover);
+}
+
+// Checks if callouts installed on buffer4_send can set skip flag and that flag
+// causes the packet to not be sent
+TEST_F(HooksDhcpv4SrvTest, buffer4SendSkip) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer4_send", skip_callout));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr discover = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(discover);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_receive callback.
+ srv_->run();
+
+ // 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(discover);
+}
+
+// Checks if callouts installed on buffer4_send can set drop flag and that flag
+// causes the packet to not be sent
+TEST_F(HooksDhcpv4SrvTest, buffer4SendDrop) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer4_send", drop_callout));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr discover = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(discover);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_receive callback.
+ srv_->run();
+
+ // 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(discover);
+}
+
+
+// This test checks if subnet4_select callout is triggered and reports
+// valid parameters
+TEST_F(HooksDhcpv4SrvTest, subnet4SelectSimple) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Configure 2 subnets, both directly reachable over local interface
+ // (let's not complicate the matter with relays)
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.0/25\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"interface\": \"eth0\" "
+ " }, {"
+ " \"pools\": [ { \"pool\": \"192.0.3.0/25\" } ],"
+ " \"subnet\": \"192.0.3.0/24\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ EXPECT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ // Commit the config
+ CfgMgr::instance().commit();
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "subnet4_select", subnet4_select_callout));
+
+ // Prepare discover packet. Server should select first subnet for it
+ Pkt4Ptr sol = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ sol->setRemoteAddr(IOAddress("192.0.2.1"));
+ sol->setIface("eth1");
+ sol->setIndex(ETH1_INDEX);
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ Pkt4Ptr adv = srv_->processDiscover(sol);
+
+ // check if we get response at all
+ ASSERT_TRUE(adv);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("subnet4_select", callback_name_);
+
+ // Check that pkt4 argument passing was successful and returned proper value
+ EXPECT_TRUE(callback_qry_pkt4_.get() == sol.get());
+
+ const Subnet4Collection* exp_subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+
+ // The server is supposed to pick the first subnet, because of matching
+ // interface. Check that the value is reported properly.
+ ASSERT_TRUE(callback_subnet4_);
+ EXPECT_EQ(exp_subnets->begin()->get(), callback_subnet4_.get());
+
+ // Server is supposed to report two subnets
+ ASSERT_EQ(exp_subnets->size(), callback_subnet4collection_->size());
+ ASSERT_GE(exp_subnets->size(), 2);
+
+ // Compare that the available subnets are reported as expected
+ EXPECT_TRUE((*exp_subnets->begin())->get() == (*callback_subnet4collection_->begin())->get());
+ EXPECT_TRUE((*std::next(exp_subnets->begin()))->get() == (*std::next(callback_subnet4collection_->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 subnet4_select hook point can pick
+// a different subnet.
+TEST_F(HooksDhcpv4SrvTest, subnet4SelectChange) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Configure 2 subnets, both directly reachable over local interface
+ // (let's not complicate the matter with relays)
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.0/25\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"interface\": \"eth0\" "
+ " }, {"
+ " \"pools\": [ { \"pool\": \"192.0.3.0/25\" } ],"
+ " \"subnet\": \"192.0.3.0/24\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ EXPECT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "subnet4_select", subnet4_select_different_subnet_callout));
+
+ // Prepare discover packet. Server should select first subnet for it
+ Pkt4Ptr sol = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ sol->setRemoteAddr(IOAddress("192.0.2.1"));
+ sol->setIface("eth0");
+ sol->setIndex(ETH0_INDEX);
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ Pkt4Ptr adv = srv_->processDiscover(sol);
+
+ // check if we get response at all
+ ASSERT_TRUE(adv);
+
+ // The response should have an address from second pool, so let's check it
+ IOAddress addr = adv->getYiaddr();
+ EXPECT_NE("0.0.0.0", addr.toText());
+
+ // Get all subnets and use second subnet for verification
+ const Subnet4Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->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));
+ EXPECT_TRUE((*subnet)->inPool(Lease::TYPE_V4, addr));
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// This test verifies that the leases4_committed hook point is not triggered
+// for the DHCPDISCOVER.
+TEST_F(HooksDhcpv4SrvTest, leases4CommittedDiscover) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases4_committed", leases4_committed_callout));
+
+
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.setIfaceName("eth1");
+ client.setIfaceIndex(ETH1_INDEX);
+ ASSERT_NO_THROW(client.doDiscover());
+
+ // 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 leases4_committed hook point is not triggered
+// for the DHCPINFORM.
+TEST_F(HooksDhcpv4SrvTest, leases4CommittedInform) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases4_committed", leases4_committed_callout));
+
+
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.useRelay();
+ ASSERT_NO_THROW(client.doInform());
+
+ // 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 incoming (positive) REQUEST/Renewing can be handled
+// properly and that callout installed on lease4_renew is triggered with
+// expected parameters.
+TEST_F(HooksDhcpv4SrvTest, lease4RenewSimple) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ const IOAddress addr("192.0.2.106");
+ const uint32_t temp_valid = 100;
+ const time_t temp_timestamp = time(NULL) - 10;
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease4_renew", lease4_renew_callout));
+
+ // Generate client-id also sets client_id_ member
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_V4, addr));
+
+ // let's create a lease and put it in the LeaseMgr
+ uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER));
+ Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2,
+ &client_id_->getDuid()[0], client_id_->getDuid().size(),
+ temp_valid, temp_timestamp, subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database
+ Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RENEW
+ Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+ req->setRemoteAddr(IOAddress(addr));
+ req->setYiaddr(addr);
+ req->setCiaddr(addr); // client's address
+ req->setIface("eth0");
+ req->setIndex(ETH0_INDEX);
+ req->setHWAddr(hwaddr2);
+
+ req->addOption(clientid);
+ req->addOption(srv_->getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt4Ptr ack = srv_->processRequest(req);
+
+ // Check if we get response at all
+ checkResponse(ack, DHCPACK, 1234);
+
+ // Check that the lease is really in the database
+ l = checkLease(ack, clientid, req->getHWAddr(), addr);
+ ASSERT_TRUE(l);
+
+ // Check that preferred, valid and cltt were really updated
+ EXPECT_EQ(l->valid_lft_, subnet_->getValid());
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("lease4_renew", callback_name_);
+
+ // Check that query4 argument passing was successful and
+ // returned proper value
+ EXPECT_TRUE(callback_qry_pkt4_.get() == req.get());
+
+ // Check that hwaddr parameter is passed properly
+ ASSERT_TRUE(callback_hwaddr_);
+ EXPECT_TRUE(*callback_hwaddr_ == *req->getHWAddr());
+
+ // Check that the subnet is passed properly
+ ASSERT_TRUE(callback_subnet4_);
+ EXPECT_EQ(callback_subnet4_->toText(), subnet_->toText());
+
+ ASSERT_TRUE(callback_clientid_);
+ ASSERT_TRUE(client_id_);
+ EXPECT_TRUE(*client_id_ == *callback_clientid_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query4");
+ expected_argument_names.push_back("subnet4");
+ expected_argument_names.push_back("clientid");
+ expected_argument_names.push_back("hwaddr");
+ expected_argument_names.push_back("lease4");
+ 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);
+
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(addr);
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(lease));
+
+ // 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 a callout installed on lease4_renew can trigger
+// the server to not renew a lease.
+TEST_F(HooksDhcpv4SrvTest, lease4RenewSkip) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ const IOAddress addr("192.0.2.106");
+ const uint32_t temp_valid = 100;
+ const time_t temp_timestamp = time(NULL) - 10;
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease4_renew", skip_callout));
+
+ // Generate client-id also sets client_id_ member
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_V4, addr));
+
+ // let's create a lease and put it in the LeaseMgr
+ uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER));
+ Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2,
+ &client_id_->getDuid()[0], client_id_->getDuid().size(),
+ temp_valid, temp_timestamp, subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database
+ Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // Check that preferred, valid and cltt really set.
+ // Constructed lease looks as if it was assigned 10 seconds ago
+ EXPECT_EQ(l->valid_lft_, temp_valid);
+ EXPECT_EQ(l->cltt_, temp_timestamp);
+
+ // Let's create a RENEW
+ Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+ req->setRemoteAddr(IOAddress(addr));
+ req->setYiaddr(addr);
+ req->setCiaddr(addr); // client's address
+ req->setIface("eth0");
+ req->setIndex(ETH0_INDEX);
+ req->setHWAddr(hwaddr2);
+
+ req->addOption(clientid);
+ req->addOption(srv_->getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt4Ptr ack = srv_->processRequest(req);
+ ASSERT_TRUE(ack);
+
+ // Check that the lease is really in the database
+ l = checkLease(ack, clientid, req->getHWAddr(), addr);
+ ASSERT_TRUE(l);
+
+ // Check that valid and cltt were NOT updated
+ EXPECT_EQ(temp_valid, l->valid_lft_);
+ EXPECT_EQ(temp_timestamp, l->cltt_);
+
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(addr);
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(lease));
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(req);
+}
+
+// This test verifies that the callout installed on the leases4_committed hook
+// point is executed as a result of DHCPREQUEST message sent to allocate new
+// lease or renew an existing lease.
+TEST_F(HooksDhcpv4SrvTest, leases4CommittedRequest) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases4_committed", leases4_committed_callout));
+
+
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.setIfaceName("eth1");
+ client.setIfaceIndex(ETH1_INDEX);
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.100"))));
+
+ // 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("leases4_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query4");
+ expected_argument_names.push_back("deleted_leases4");
+ expected_argument_names.push_back("leases4");
+
+ 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_lease4_);
+ EXPECT_EQ("192.0.2.100", callback_lease4_->addr_.toText());
+
+ // Deleted lease must not be present, because it is a new allocation.
+ EXPECT_FALSE(callback_deleted_lease4_);
+
+ // 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.
+ client.setState(Dhcp4Client::RENEWING);
+ 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("leases4_committed", callback_name_);
+
+ // Renewed lease should be returned.
+ ASSERT_TRUE(callback_lease4_);
+ EXPECT_EQ("192.0.2.100", callback_lease4_->addr_.toText());
+
+ // Deleted lease must not be present, because it is a new allocation.
+ EXPECT_FALSE(callback_deleted_lease4_);
+
+ // 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 request a different
+ // address.
+ client.ciaddr_ = IOAddress("192.0.2.101");
+
+ 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("leases4_committed", callback_name_);
+
+ // New lease should be returned.
+ ASSERT_TRUE(callback_lease4_);
+ EXPECT_EQ("192.0.2.101", callback_lease4_->addr_.toText());
+
+ // The old lease should have been deleted.
+ ASSERT_TRUE(callback_deleted_lease4_);
+ EXPECT_EQ("192.0.2.100", callback_deleted_lease4_->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_);
+
+ resetCalloutBuffers();
+
+ // Now request an address that can't be allocated.
+ client.ciaddr_ = IOAddress("10.0.0.1");
+
+ ASSERT_NO_THROW(client.doRequest());
+
+ // Make sure that we did not receive a response. Since we're
+ // not authoritative, there should not be a DHCPNAK.
+ ASSERT_FALSE(client.getContext().response_);
+
+ // Check that no callback was not called.
+ EXPECT_EQ("", callback_name_);
+ EXPECT_FALSE(callback_lease4_);
+ EXPECT_FALSE(callback_deleted_lease4_);
+}
+
+// This test verifies that the callout installed on the leases4_committed hook
+// point is executed as a result of DHCPREQUEST message sent to reuse
+// an existing lease.
+TEST_F(HooksDhcpv4SrvTest, leases4CommittedCache) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases4_committed", leases4_committed_callout));
+
+
+ // Modify the subnet to reuse leases.
+ subnet_->setCacheThreshold(.25);
+
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.setIfaceName("eth1");
+ client.setIfaceIndex(ETH1_INDEX);
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.100"))));
+
+ // 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("leases4_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query4");
+ expected_argument_names.push_back("deleted_leases4");
+ expected_argument_names.push_back("leases4");
+
+ 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_lease4_);
+ EXPECT_EQ("192.0.2.100", callback_lease4_->addr_.toText());
+
+ // Deleted lease must not be present, because it is a new allocation.
+ EXPECT_FALSE(callback_deleted_lease4_);
+
+ // 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.
+ client.setState(Dhcp4Client::RENEWING);
+ 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("leases4_committed", callback_name_);
+
+ // Renewed lease should not be present because it was reused.
+ EXPECT_FALSE(callback_lease4_);
+
+ // Deleted lease must not be present, because it renews the same address.
+ EXPECT_FALSE(callback_deleted_lease4_);
+
+ // 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 leases4_committed callouts.
+TEST_F(HooksDhcpv4SrvTest, leases4CommittedParkRequests) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // 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(
+ "leases4_committed", leases4_committed_park_callout));
+
+ // Create first client and perform DORA.
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ client1.setIfaceName("eth1");
+ client1.setIfaceIndex(ETH1_INDEX);
+ ASSERT_NO_THROW(client1.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.100"))));
+
+ // We should be offered an address but the DHCPACK 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("leases4_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query4");
+ expected_argument_names.push_back("deleted_leases4");
+ expected_argument_names.push_back("leases4");
+
+ 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_lease4_);
+ EXPECT_EQ("192.0.2.100", callback_lease4_->addr_.toText());
+
+ // Deleted lease must not be present, because it is a new allocation.
+ EXPECT_FALSE(callback_deleted_lease4_);
+
+ // 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.
+ Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+ client2.setIfaceName("eth1");
+ client2.setIfaceIndex(ETH1_INDEX);
+ ASSERT_NO_THROW(client2.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.101"))));
+
+ // The DHCPOFFER should have been returned but not DHCPACK, as this
+ // packet got parked too.
+ ASSERT_FALSE(client2.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed.
+ EXPECT_EQ("leases4_committed", callback_name_);
+
+ // There should be now two actions scheduled on our IO service
+ // by the invoked callouts. They unpark both DHCPACK messages.
+ ASSERT_NO_THROW(io_service_->poll());
+
+ // Receive and check the first response.
+ ASSERT_NO_THROW(client1.receiveResponse());
+ ASSERT_TRUE(client1.getContext().response_);
+ Pkt4Ptr rsp = client1.getContext().response_;
+ EXPECT_EQ(DHCPACK, rsp->getType());
+ EXPECT_EQ("192.0.2.100", rsp->getYiaddr().toText());
+
+ // Receive and check the second response.
+ ASSERT_NO_THROW(client2.receiveResponse());
+ ASSERT_TRUE(client2.getContext().response_);
+ rsp = client2.getContext().response_;
+ EXPECT_EQ(DHCPACK, rsp->getType());
+ EXPECT_EQ("192.0.2.101", rsp->getYiaddr().toText());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client2.getContext().query_);
+}
+
+// This test verifies that valid RELEASE triggers lease4_release callouts
+TEST_F(HooksDhcpv4SrvTest, lease4ReleaseSimple) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ const IOAddress addr("192.0.2.106");
+ const uint32_t temp_valid = 100;
+ const time_t temp_timestamp = time(NULL) - 10;
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease4_release", lease4_release_callout));
+
+ // 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_V4, addr));
+
+ // Let's create a lease and put it in the LeaseMgr
+ uint8_t mac_addr[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ HWAddrPtr hw(new HWAddr(mac_addr, sizeof(mac_addr), HTYPE_ETHER));
+ Lease4Ptr used(new Lease4(addr, hw,
+ &client_id_->getDuid()[0], client_id_->getDuid().size(),
+ temp_valid, temp_timestamp, subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database
+ Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ // Generate client-id also duid_
+ Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
+ rel->setRemoteAddr(addr);
+ rel->setCiaddr(addr);
+ rel->addOption(clientid);
+ rel->addOption(srv_->getServerID());
+ rel->setHWAddr(hw);
+
+ // Pass it to the server and hope for a REPLY
+ // Note: this is no response to RELEASE in DHCPv4
+ EXPECT_NO_THROW(srv_->processRelease(rel));
+
+ // The lease should be gone from LeaseMgr
+ l = LeaseMgrFactory::instance().getLease4(addr);
+ EXPECT_FALSE(l);
+
+ // Try to get the lease by hardware address
+ // @todo: Uncomment this once trac2592 is implemented
+ // Lease4Collection leases = LeaseMgrFactory::instance().getLease4(hw->hwaddr_);
+ // EXPECT_EQ(leases.size(), 0);
+
+ // Try to get it by hw/subnet_id combination
+ l = LeaseMgrFactory::instance().getLease4(hw->hwaddr_, subnet_->getID());
+ EXPECT_FALSE(l);
+
+ // Try by client-id
+ // @todo: Uncomment this once trac2592 is implemented
+ //Lease4Collection leases = LeaseMgrFactory::instance().getLease4(*client_id_);
+ //EXPECT_EQ(leases.size(), 0);
+
+ // Try by client-id/subnet-id
+ l = LeaseMgrFactory::instance().getLease4(*client_id_, subnet_->getID());
+ EXPECT_FALSE(l);
+
+ // Ok, the lease is *really* not there.
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("lease4_release", callback_name_);
+
+ // Check that pkt4 argument passing was successful and returned proper value
+ EXPECT_TRUE(callback_qry_pkt4_.get() == rel.get());
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query4");
+ expected_argument_names.push_back("lease4");
+ 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);
+
+ // 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 skip flag returned by a callout installed on the
+// lease4_release hook point will keep the lease
+TEST_F(HooksDhcpv4SrvTest, lease4ReleaseSkip) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ const IOAddress addr("192.0.2.106");
+ const uint32_t temp_valid = 100;
+ const time_t temp_timestamp = time(NULL) - 10;
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease4_release", skip_callout));
+
+ // 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_V4, addr));
+
+ // Let's create a lease and put it in the LeaseMgr
+ uint8_t mac_addr[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ HWAddrPtr hw(new HWAddr(mac_addr, sizeof(mac_addr), HTYPE_ETHER));
+ Lease4Ptr used(new Lease4(addr, hw,
+ &client_id_->getDuid()[0], client_id_->getDuid().size(),
+ temp_valid, temp_timestamp, subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database
+ Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ // Generate client-id also duid_
+ Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
+ rel->setRemoteAddr(addr);
+ rel->setYiaddr(addr);
+ rel->addOption(clientid);
+ rel->addOption(srv_->getServerID());
+ rel->setHWAddr(hw);
+
+ // Pass it to the server and hope for a REPLY
+ // Note: this is no response to RELEASE in DHCPv4
+ EXPECT_NO_THROW(srv_->processRelease(rel));
+
+ // The lease should be still there
+ l = LeaseMgrFactory::instance().getLease4(addr);
+ EXPECT_TRUE(l);
+
+ // Try by client-id/subnet-id
+ l = LeaseMgrFactory::instance().getLease4(*client_id_, subnet_->getID());
+ EXPECT_TRUE(l);
+
+ // Try to get the lease by hardware address, should succeed
+ Lease4Collection leases = LeaseMgrFactory::instance().getLease4(*hw);
+ EXPECT_EQ(leases.size(), 1);
+
+ // Try by client-id, should be successful as well.
+ leases = LeaseMgrFactory::instance().getLease4(*client_id_);
+ EXPECT_EQ(leases.size(), 1);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(rel);
+}
+
+// This test verifies that the leases4_committed callout is executed
+// with deleted leases as argument when DHCPRELEASE is processed.
+TEST_F(HooksDhcpv4SrvTest, leases4CommittedRelease) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases4_committed", leases4_committed_callout));
+
+
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.setIfaceName("eth1");
+ client.setIfaceIndex(ETH1_INDEX);
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.100"))));
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ resetCalloutBuffers();
+
+ ASSERT_NO_THROW(client.doRelease());
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases4_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query4");
+ expected_argument_names.push_back("deleted_leases4");
+ expected_argument_names.push_back("leases4");
+
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // No new allocations.
+ EXPECT_FALSE(callback_lease4_);
+
+ // Released lease should be returned.
+ ASSERT_TRUE(callback_deleted_lease4_);
+ EXPECT_EQ("192.0.2.100", callback_deleted_lease4_->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 drop flag returned by a callout installed on the
+// lease4_release hook point will keep the lease
+TEST_F(HooksDhcpv4SrvTest, lease4ReleaseDrop) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ const IOAddress addr("192.0.2.106");
+ const uint32_t temp_valid = 100;
+ const time_t temp_timestamp = time(NULL) - 10;
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease4_release", drop_callout));
+
+ // 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_V4, addr));
+
+ // Let's create a lease and put it in the LeaseMgr
+ uint8_t mac_addr[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ HWAddrPtr hw(new HWAddr(mac_addr, sizeof(mac_addr), HTYPE_ETHER));
+ Lease4Ptr used(new Lease4(addr, hw,
+ &client_id_->getDuid()[0], client_id_->getDuid().size(),
+ temp_valid, temp_timestamp, subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database
+ Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ // Generate client-id also duid_
+ Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
+ rel->setRemoteAddr(addr);
+ rel->setYiaddr(addr);
+ rel->addOption(clientid);
+ rel->addOption(srv_->getServerID());
+ rel->setHWAddr(hw);
+
+ // Pass it to the server and hope for a REPLY
+ // Note: this is no response to RELEASE in DHCPv4
+ EXPECT_NO_THROW(srv_->processRelease(rel));
+
+ // The lease should be still there
+ l = LeaseMgrFactory::instance().getLease4(addr);
+ EXPECT_TRUE(l);
+
+ // Try by client-id/subnet-id
+ l = LeaseMgrFactory::instance().getLease4(*client_id_, subnet_->getID());
+ EXPECT_TRUE(l);
+
+ // Try to get the lease by hardware address, should succeed
+ Lease4Collection leases = LeaseMgrFactory::instance().getLease4(*hw);
+ EXPECT_EQ(leases.size(), 1);
+
+ // Try by client-id, should be successful as well.
+ leases = LeaseMgrFactory::instance().getLease4(*client_id_);
+ EXPECT_EQ(leases.size(), 1);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(rel);
+}
+
+// Checks that decline4 hooks (lease4_decline) are triggered properly.
+TEST_F(HooksDhcpv4SrvTest, HooksDecline) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease4_decline", lease4_decline_callout));
+
+ HooksManager::setTestMode(true);
+
+ // Conduct the actual DORA + Decline.
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ acquireAndDecline(client, "01:02:03:04:05:06", "12:14",
+ "01:02:03:04:05:06", "12:14",
+ SHOULD_PASS);
+
+ EXPECT_EQ("lease4_decline", callback_name_);
+
+ // Verifying DHCPDECLINE is a bit tricky, as it is created somewhere in
+ // acquireAndDecline. We'll just verify that it's really a DECLINE
+ // and that its address is equal to what we have in LeaseMgr.
+ ASSERT_TRUE(callback_qry_pkt4_);
+ ASSERT_TRUE(callback_lease4_);
+
+ // Check that it's the proper packet that was reported.
+ EXPECT_EQ(DHCPDECLINE, callback_qry_pkt4_->getType());
+
+ // Extract the address being declined.
+ OptionCustomPtr opt_declined_addr = boost::dynamic_pointer_cast<
+ OptionCustom>(callback_qry_pkt4_->getOption(DHO_DHCP_REQUESTED_ADDRESS));
+ ASSERT_TRUE(opt_declined_addr);
+ IOAddress addr(opt_declined_addr->readAddress());
+
+ // And try to get a matching lease from the lease manager.
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(from_mgr);
+ EXPECT_EQ(Lease::STATE_DECLINED, from_mgr->state_);
+
+ // Let's now check that those 3 things (packet, lease returned and lease from
+ // the lease manager) all match.
+ EXPECT_EQ(addr, from_mgr->addr_);
+ EXPECT_EQ(addr, callback_lease4_->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_);
+}
+
+// Checks that decline4 hook is able to skip the packet.
+TEST_F(HooksDhcpv4SrvTest, HooksDeclineSkip) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease4_decline", lease4_decline_skip_callout));
+
+ HooksManager::setTestMode(true);
+
+ // Conduct the actual DORA + Decline. The DECLINE should fail, as the
+ // hook will set the status to SKIP.
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ acquireAndDecline(client, "01:02:03:04:05:06", "12:14",
+ "01:02:03:04:05:06", "12:14",
+ SHOULD_FAIL);
+
+ EXPECT_EQ("lease4_decline", callback_name_);
+
+ // Verifying DHCPDECLINE is a bit tricky, as it is created somewhere in
+ // acquireAndDecline. We'll just verify that it's really a DECLINE
+ // and that its address is equal to what we have in LeaseMgr.
+ ASSERT_TRUE(callback_qry_pkt4_);
+ ASSERT_TRUE(callback_lease4_);
+
+ // Check that it's the proper packet that was reported.
+ EXPECT_EQ(DHCPDECLINE, callback_qry_pkt4_->getType());
+
+ // Extract the address being declined.
+ OptionCustomPtr opt_declined_addr = boost::dynamic_pointer_cast<
+ OptionCustom>(callback_qry_pkt4_->getOption(DHO_DHCP_REQUESTED_ADDRESS));
+ ASSERT_TRUE(opt_declined_addr);
+ IOAddress addr(opt_declined_addr->readAddress());
+
+ // And try to get a matching lease from the lease manager. The lease should
+ // still be there in default state, not in declined state.
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(from_mgr);
+ EXPECT_EQ(Lease::STATE_DEFAULT, from_mgr->state_);
+
+ // As a final sanity check, let's now check that those 3 things (packet,
+ // lease returned and lease from the lease manager) all match.
+ EXPECT_EQ(addr, from_mgr->addr_);
+ EXPECT_EQ(addr, callback_lease4_->addr_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// Checks that decline4 hook is able to drop the packet.
+TEST_F(HooksDhcpv4SrvTest, HooksDeclineDrop) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease4_decline", lease4_decline_drop_callout));
+
+ HooksManager::setTestMode(true);
+
+ // Conduct the actual DORA + Decline. The DECLINE should fail, as the
+ // hook will set the status to DROP.
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ acquireAndDecline(client, "01:02:03:04:05:06", "12:14",
+ "01:02:03:04:05:06", "12:14",
+ SHOULD_FAIL);
+
+ EXPECT_EQ("lease4_decline", callback_name_);
+
+ // Verifying DHCPDECLINE is a bit tricky, as it is created somewhere in
+ // acquireAndDecline. We'll just verify that it's really a DECLINE
+ // and that its address is equal to what we have in LeaseMgr.
+ ASSERT_TRUE(callback_qry_pkt4_);
+ ASSERT_TRUE(callback_lease4_);
+
+ // Check that it's the proper packet that was reported.
+ EXPECT_EQ(DHCPDECLINE, callback_qry_pkt4_->getType());
+
+ // Extract the address being declined.
+ OptionCustomPtr opt_declined_addr = boost::dynamic_pointer_cast<
+ OptionCustom>(callback_qry_pkt4_->getOption(DHO_DHCP_REQUESTED_ADDRESS));
+ ASSERT_TRUE(opt_declined_addr);
+ IOAddress addr(opt_declined_addr->readAddress());
+
+ // And try to get a matching lease from the lease manager. The lease should
+ // still be there in default state, not in declined state.
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(from_mgr);
+ EXPECT_EQ(Lease::STATE_DEFAULT, from_mgr->state_);
+
+ // As a final sanity check, let's now check that those 3 things (packet,
+ // lease returned and lease from the lease manager) all match.
+ EXPECT_EQ(addr, from_mgr->addr_);
+ EXPECT_EQ(addr, callback_lease4_->addr_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// This test verifies that the leases4_committed callout is executed
+// with declined leases as argument when DHCPDECLINE is processed.
+TEST_F(HooksDhcpv4SrvTest, leases4CommittedDecline) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases4_committed", leases4_committed_callout));
+
+
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.useRelay();
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.100"))));
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ resetCalloutBuffers();
+
+ ASSERT_NO_THROW(client.doDecline());
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases4_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query4");
+ expected_argument_names.push_back("deleted_leases4");
+ expected_argument_names.push_back("leases4");
+
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // No new allocations.
+ ASSERT_TRUE(callback_lease4_);
+ EXPECT_EQ("192.0.2.100", callback_lease4_->addr_.toText());
+ EXPECT_EQ(Lease::STATE_DECLINED, callback_lease4_->state_);
+
+ // Released lease should be returned.
+ EXPECT_FALSE(callback_deleted_lease4_);
+
+ // 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_);
+}
+
+// Checks if callout installed on host4_identifier can generate an
+// identifier and whether that identifier is actually used.
+TEST_F(HooksDhcpv4SrvTest, host4_identifier) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Configure a subnet with host reservation. The reservation is based on
+ // flexible identifier value of 'foo'. That's exactly what the
+ // host4_identifier_foo_callout sets.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"host-reservation-identifiers\": [ \"flex-id\" ], "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.0/25\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"interface\": \"eth0\", "
+ " \"reservations\": ["
+ " {"
+ " \"flex-id\": \"'foo'\","
+ " \"ip-address\": \"192.0.2.201\""
+ " }"
+ " ]"
+ "} ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ EXPECT_NO_THROW(json = parseDHCP4(config));
+ ASSERT_TRUE(json);
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Install host4_identifier_foo_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "host4_identifier", host4_identifier_foo_callout));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr sol = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_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
+ Pkt4Ptr adv = srv_->fake_sent_.front();
+ ASSERT_TRUE(adv);
+
+ // Make sure the address offered is the one that was reserved.
+ EXPECT_EQ("192.0.2.201", adv->getYiaddr().toText());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callout installed on host4_identifier can generate identifier of
+// other type. This particular callout always returns hwaddr.
+TEST_F(HooksDhcpv4SrvTest, host4_identifier_hwaddr) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ // Configure a subnet with host reservation. The reservation is based on
+ // flexible identifier value of 'foo'. That's exactly what the
+ // host4_identifier_foo_callout sets.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"host-reservation-identifiers\": [ \"flex-id\" ], "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.0/25\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"interface\": \"eth0\", "
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"00:01:02:03:04:05\","
+ " \"ip-address\": \"192.0.2.201\""
+ " }"
+ " ]"
+ "} ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ EXPECT_NO_THROW(json = parseDHCP4(config));
+ ASSERT_TRUE(json);
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Install host4_identifier_hwaddr_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "host4_identifier", host4_identifier_hwaddr_callout));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr sol = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_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
+ Pkt4Ptr adv = srv_->fake_sent_.front();
+ ASSERT_TRUE(adv);
+
+ // Make sure the address offered is the one that was reserved.
+ EXPECT_EQ("192.0.2.201", adv->getYiaddr().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(LoadUnloadDhcpv4SrvTest, unloadLibraries) {
+
+ ASSERT_NO_THROW(server_.reset(new NakedDhcpv4Srv()));
+
+ // 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(LoadUnloadDhcpv4SrvTest, failLoadIncompatibleLibraries) {
+
+ ASSERT_NO_THROW(server_.reset(new NakedDhcpv4Srv()));
+
+ MultiThreadingMgr::instance().setMode(true);
+
+ // 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));
+
+ // 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));
+
+ // 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 dhcp4_srv_configured ared indeed called
+// and all the necessary parameters are passed.
+TEST_F(LoadUnloadDhcpv4SrvTest, Dhcpv4SrvConfigured) {
+ for (string parameters : {
+ "",
+ R"(, "parameters": { "mode": "fail-without-error" } )",
+ R"(, "parameters": { "mode": "fail-with-error" } )"}) {
+
+ reset();
+
+ boost::shared_ptr<ControlledDhcpv4Srv> srv(new ControlledDhcpv4Srv(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
+ // dhcp4_srv_configured callout.
+ string config_str =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ ]"
+ " },"
+ " \"rebind-timer\": 2000,"
+ " \"renew-timer\": 1000,"
+ " \"subnet4\": [ ],"
+ " \"valid-lifetime\": 4000,"
+ " \"lease-database\": {"
+ " \"type\": \"memfile\","
+ " \"persist\": false"
+ " },"
+ " \"hooks-libraries\": ["
+ " {"
+ " \"library\": \"" + std::string(CALLOUT_LIBRARY_3) + "\""
+ + parameters +
+ " }"
+ " ]"
+ "}";
+
+ 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);
+ EXPECT_EQ(answer->str(), R"({ "result": 0, "text": "Configuration successful." })");
+ } 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 dhcp4_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(HooksDhcpv4SrvTest, parkedPacketLimit) {
+ IfaceMgrTestConfig test_config(true);
+
+ // Configure 1 directly reachable subnet, parked-packet-limit of 1.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"parked-packet-limit\": 1,"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.0/24\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"interface\": \"eth1\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ EXPECT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ // Commit the config
+ CfgMgr::instance().commit();
+ IfaceMgr::instance().openSockets4();
+
+ // This callout uses the provided IO service object to post a function
+ // that unparks the packet. Once the packet is parked, it can be unparked
+ // by simply calling IOService::poll.
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases4_committed", leases4_committed_park_callout));
+
+ // Statistic should not show any drops.
+ EXPECT_EQ(0, getStatistic("pkt4-receive-drop"));
+
+ // Create a client and initiate a DORA cycle for it.
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.setIfaceName("eth1");
+ client.setIfaceIndex(ETH1_INDEX);
+ ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.100"))));
+
+ // Check that the callback called is indeed the one we installed
+ ASSERT_EQ("leases4_committed", callback_name_);
+
+ // Make sure that we have not received a response.
+ ASSERT_FALSE(client.getContext().response_);
+
+ // Verify we have a packet parked.
+ const auto& parking_lot = ServerHooks::getServerHooks().getParkingLotPtr("leases4_committed");
+ ASSERT_TRUE(parking_lot);
+ ASSERT_EQ(1, parking_lot->size());
+
+ // Clear callout buffers.
+ resetCalloutBuffers();
+
+ // Create a second client and initiate a DORA for it.
+ // Since the parking lot limit has been reached, the packet
+ // should be dropped with no response.
+ Dhcp4Client client2(Dhcp4Client::SELECTING);
+ client2.setIfaceName("eth1");
+ client2.setIfaceIndex(ETH1_INDEX);
+ ASSERT_NO_THROW(client2.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.101"))));
+
+ // Check that no callback was called.
+ ASSERT_EQ("", callback_name_);
+
+ // Make sure that we have not received a response.
+ ASSERT_FALSE(client2.getContext().response_);
+
+ // Verify we have not parked another packet.
+ ASSERT_EQ(1, parking_lot->size());
+
+ // Statistic should show one drop.
+ EXPECT_EQ(1, getStatistic("pkt4-receive-drop"));
+
+ // Invoking poll should run the scheduled action only for
+ // the first client.
+ ASSERT_NO_THROW(io_service_->poll());
+
+ // Receive and check the first response.
+ ASSERT_NO_THROW(client.receiveResponse());
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr rsp = client.getContext().response_;
+ EXPECT_EQ(DHCPACK, rsp->getType());
+ EXPECT_EQ("192.0.2.100", rsp->getYiaddr().toText());
+
+ // Verify we have no parked packets.
+ ASSERT_EQ(0, parking_lot->size());
+
+ resetCalloutBuffers();
+
+ // Try client2 again.
+ ASSERT_NO_THROW(client2.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.101"))));
+
+ // Check that the callback called is indeed the one we installed
+ ASSERT_EQ("leases4_committed", callback_name_);
+
+ // Make sure that we have not received a response.
+ ASSERT_FALSE(client2.getContext().response_);
+
+ // Verify we parked the packet.
+ ASSERT_EQ(1, parking_lot->size());
+
+ // Invoking poll should run the scheduled action.
+ ASSERT_NO_THROW(io_service_->poll());
+
+ // Receive and check the first response.
+ ASSERT_NO_THROW(client2.receiveResponse());
+ ASSERT_TRUE(client2.getContext().response_);
+ rsp = client2.getContext().response_;
+ EXPECT_EQ(DHCPACK, rsp->getType());
+ EXPECT_EQ("192.0.2.101", rsp->getYiaddr().toText());
+
+ // Verify we have no parked packets.
+ ASSERT_EQ(0, parking_lot->size());
+
+ // Statistic should still show one drop.
+ EXPECT_EQ(1, getStatistic("pkt4-receive-drop"));
+}
+
diff --git a/src/bin/dhcp4/tests/host_options_unittest.cc b/src/bin/dhcp4/tests/host_options_unittest.cc
new file mode 100644
index 0000000..db6ccb1
--- /dev/null
+++ b/src/bin/dhcp4/tests/host_options_unittest.cc
@@ -0,0 +1,554 @@
+// Copyright (C) 2016-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/dhcp4.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp4/tests/dhcp4_client.h>
+#include <stats/stats_mgr.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Boolean value used to signal stateless configuration test.
+const bool STATELESS = true;
+
+/// @brief Boolean value used to signal stateful configuration test.
+const bool STATEFUL = false;
+
+/// @brief Set of JSON configurations used throughout the tests.
+///
+/// - Configuration 0:
+/// - Used to test that host specific options override subnet specific
+/// options when these options are requested with PRL option.
+/// - Single subnet 10.0.0.0/24 with a pool of 10.0.0.10-10.0.0.100
+/// - 4 options configured within subnet scope
+/// - routers: 10.0.0.200,10.0.0.201,
+/// - domain-name-servers: 10.0.0.202,10.0.0.203,
+/// - log-servers: 10.0.0.200,10.0.0.201,
+/// - cookie-servers: 10.0.0.202,10.0.0.203
+/// - Single reservation within the subnet:
+/// - HW address: aa:bb:cc:dd:ee:ff
+/// - ip-address: 10.0.0.7
+/// - Two options overriding subnet specific options:
+/// - cookie-servers: 10.1.1.202, 10.1.1.203
+/// - log-servers: 10.1.1.200, 10.1.1.201
+///
+/// - Configuration 1:
+/// - Used to test that host specific options override subnet specific
+/// default options. Default options are those that are sent even when
+/// not requested by a client.
+/// - Single subnet 10.0.0.0/24 with a pool of 10.0.0.10-10.0.0.100
+/// - 4 options configured within subnet scope
+/// - routers: 10.0.0.200,10.0.0.201,
+/// - domain-name-servers: 10.0.0.202,10.0.0.203,
+/// - log-servers: 10.0.0.200,10.0.0.201,
+/// - cookie-servers: 10.0.0.202,10.0.0.203
+/// - Single reservation within the subnet:
+/// - HW address: aa:bb:cc:dd:ee:ff
+/// - ip-address: 10.0.0.7
+/// - Two options overriding subnet specific default options:
+/// - routers: 10.1.1.200, 10.1.1.201
+/// - domain-name-servers: 10.1.1.202, 10.1.1.203
+///
+/// - Configuration 2:
+/// - Used to test that client receives options solely specified in a
+/// host scope.
+/// - Single reservation within the subnet:
+/// - HW address: aa:bb:cc:dd:ee:ff
+/// - ip-address: 10.0.0.7
+/// - Two options:
+/// - routers: 10.1.1.200, 10.1.1.201
+/// - cookie-servers: 10.1.1.202, 10.1.1.203
+///
+/// - Configuration 3:
+/// - Used to test that host specific vendor options override globally
+/// specified vendor options.
+/// - Globally specified option 125 with Cable Labs vendor id.
+/// - TFTP servers sub option: 10.0.0.202,10.0.0.203
+/// - Single subnet 10.0.0.0/24 with a pool of 10.0.0.10-10.0.0.100
+/// - Single reservation within the subnet:
+/// - HW address: aa:bb:cc:dd:ee:ff
+/// - ip-address 10.0.0.7
+/// - Vendor option for Cable Labs vendor id specified for the reservation:
+/// - TFTP servers suboption overriding globally specified suboption:
+/// 10.1.1.202,10.1.1.203
+///
+const char* HOST_CONFIGS[] = {
+// Configuration 0
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"relay\": { \"ip-address\": \"10.0.0.233\" },"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"routers\","
+ " \"data\": \"10.0.0.200,10.0.0.201\""
+ " },"
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.202,10.0.0.203\""
+ " },"
+ " {"
+ " \"name\": \"log-servers\","
+ " \"data\": \"10.0.0.204,10.0.0.205\""
+ " },"
+ " {"
+ " \"name\": \"cookie-servers\","
+ " \"data\": \"10.0.0.206,10.0.0.207\""
+ " } ],"
+ " \"reservations\": [ "
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"10.0.0.7\","
+ " \"option-data\": [ {"
+ " \"name\": \"cookie-servers\","
+ " \"data\": \"10.1.1.202,10.1.1.203\""
+ " },"
+ " {"
+ " \"name\": \"log-servers\","
+ " \"data\": \"10.1.1.200,10.1.1.201\""
+ " } ]"
+ " } ]"
+ " } ]"
+ "}",
+
+// Configuration 1
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"relay\": { \"ip-address\": \"10.0.0.233\" },"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"routers\","
+ " \"data\": \"10.0.0.200,10.0.0.201\""
+ " },"
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.202,10.0.0.203\""
+ " },"
+ " {"
+ " \"name\": \"log-servers\","
+ " \"data\": \"10.0.0.204,10.0.0.205\""
+ " },"
+ " {"
+ " \"name\": \"cookie-servers\","
+ " \"data\": \"10.0.0.206,10.0.0.207\""
+ " } ],"
+ " \"reservations\": [ "
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"10.0.0.7\","
+ " \"option-data\": [ {"
+ " \"name\": \"routers\","
+ " \"data\": \"10.1.1.200,10.1.1.201\""
+ " },"
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.1.1.202,10.1.1.203\""
+ " } ]"
+ " } ]"
+ " } ]"
+ "}",
+
+// Configuration 2
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"relay\": { \"ip-address\": \"10.0.0.233\" },"
+ " \"reservations\": [ "
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"10.0.0.7\","
+ " \"option-data\": [ {"
+ " \"name\": \"routers\","
+ " \"data\": \"10.1.1.200,10.1.1.201\""
+ " },"
+ " {"
+ " \"name\": \"cookie-servers\","
+ " \"data\": \"10.1.1.206,10.1.1.207\""
+ " } ]"
+ " } ]"
+ " } ]"
+ "}",
+
+// Configuration 3
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"option-data\": [ {"
+ " \"name\": \"vivso-suboptions\","
+ " \"data\": \"4491\""
+ "},"
+ "{"
+ " \"name\": \"tftp-servers\","
+ " \"space\": \"vendor-4491\","
+ " \"data\": \"10.0.0.202,10.0.0.203\""
+ "} ],"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"relay\": { \"ip-address\": \"10.0.0.233\" },"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"reservations\": [ "
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"10.0.0.7\","
+ " \"option-data\": [ {"
+ " \"name\": \"vivso-suboptions\","
+ " \"data\": \"4491\""
+ " },"
+ " {"
+ " \"name\": \"tftp-servers\","
+ " \"space\": \"vendor-4491\","
+ " \"data\": \"10.1.1.202,10.1.1.203\""
+ " } ]"
+ " } ]"
+ " } ]"
+ "}"
+};
+
+/// @brief Test fixture class for testing static reservations of options.
+class HostOptionsTest : public Dhcpv4SrvTest {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets up fake interfaces.
+ HostOptionsTest()
+ : Dhcpv4SrvTest(),
+ iface_mgr_test_config_(true) {
+ }
+
+ /// @brief Verifies that host specific options override subnet specific
+ /// options.
+ ///
+ /// Overridden options are requested with Parameter Request List
+ /// option.
+ ///
+ /// @param stateless Boolean value indicating if stateless or stateful
+ /// configuration should be performed.
+ void testOverrideRequestedOptions(const bool stateless);
+
+ /// @brief Verifies that host specific options override subnet specific
+ /// options.
+ ///
+ /// Overridden options are the options which server sends regardless
+ /// if they are requested with Parameter Request List option or not.
+ ///
+ /// @param stateless Boolean value indicating if stateless or stateful
+ /// configuration should be performed.
+ void testOverrideDefaultOptions(const bool stateless);
+
+ /// @brief Verifies that client receives options when they are solely
+ /// defined in the host scope (and not in the global or subnet scope).
+ ///
+ /// @param stateless Boolean value indicating if stateless or stateful
+ /// configuration should be performed.
+ void testHostOnlyOptions(const bool stateless);
+
+ /// @brief Verifies that host specific vendor options override vendor
+ /// options defined in the global scope.
+ ///
+ /// @param stateless Boolean value indicating if stateless or stateful
+ /// configuration should be performed.
+ void testOverrideVendorOptions(const bool stateless);
+
+ /// @brief Interface Manager's fake configuration control.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+
+};
+
+void
+HostOptionsTest::testOverrideRequestedOptions(const bool stateless) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+ client.requestOptions(DHO_DOMAIN_NAME_SERVERS, DHO_LOG_SERVERS,
+ DHO_COOKIE_SERVERS);
+
+ // Configure DHCP server.
+ configure(HOST_CONFIGS[0], *client.getServer());
+
+ if (stateless) {
+ // Need to relay the message from a specific address which can
+ // be matched with a configured subnet.
+ client.useRelay(true, IOAddress("10.0.0.233"));
+ ASSERT_NO_THROW(client.doInform());
+
+ } else {
+ // Perform 4-way exchange with the server but to not request any
+ // specific address in the DHCPDISCOVER message.
+ ASSERT_NO_THROW(client.doDORA());
+ }
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ if (!stateless) {
+ // Make sure that the client has got the lease for the reserved
+ // address.
+ ASSERT_EQ("10.0.0.7", client.config_.lease_.addr_.toText());
+ }
+
+ ASSERT_EQ(2, client.config_.routers_.size());
+ EXPECT_EQ("10.0.0.200", client.config_.routers_[0].toText());
+ EXPECT_EQ("10.0.0.201", client.config_.routers_[1].toText());
+ // Make sure that the DNS Servers option has been received.
+ ASSERT_EQ(2, client.config_.dns_servers_.size());
+ EXPECT_EQ("10.0.0.202", client.config_.dns_servers_[0].toText());
+ EXPECT_EQ("10.0.0.203", client.config_.dns_servers_[1].toText());
+ // Make sure that the Quotes Servers option has been received.
+ ASSERT_EQ(2, client.config_.quotes_servers_.size());
+ EXPECT_EQ("10.1.1.202", client.config_.quotes_servers_[0].toText());
+ EXPECT_EQ("10.1.1.203", client.config_.quotes_servers_[1].toText());
+ // Make sure that the Log Servers option has been received.
+ ASSERT_EQ(2, client.config_.log_servers_.size());
+ EXPECT_EQ("10.1.1.200", client.config_.log_servers_[0].toText());
+ EXPECT_EQ("10.1.1.201", client.config_.log_servers_[1].toText());
+}
+
+void
+HostOptionsTest::testOverrideDefaultOptions(const bool stateless) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+
+ client.requestOptions(DHO_LOG_SERVERS, DHO_COOKIE_SERVERS);
+
+ // Configure DHCP server.
+ configure(HOST_CONFIGS[1], *client.getServer());
+
+ if (stateless) {
+ // Need to relay the message from a specific address which can
+ // be matched with a configured subnet.
+ client.useRelay(true, IOAddress("10.0.0.233"));
+ ASSERT_NO_THROW(client.doInform());
+
+ } else {
+ // Perform 4-way exchange with the server but to not request any
+ // specific address in the DHCPDISCOVER message.
+ ASSERT_NO_THROW(client.doDORA());
+ }
+
+ // Perform 4-way exchange with the server but to not request any
+ // specific address in the DHCPDISCOVER message.
+ ASSERT_NO_THROW(client.doDORA());
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ if (!stateless) {
+ // Make sure that the client has got the lease for the reserved
+ // address.
+ ASSERT_EQ("10.0.0.7", client.config_.lease_.addr_.toText());
+ }
+
+ ASSERT_EQ(2, client.config_.routers_.size());
+ EXPECT_EQ("10.1.1.200", client.config_.routers_[0].toText());
+ EXPECT_EQ("10.1.1.201", client.config_.routers_[1].toText());
+ // Make sure that the DNS Servers option has been received.
+ ASSERT_EQ(2, client.config_.dns_servers_.size());
+ EXPECT_EQ("10.1.1.202", client.config_.dns_servers_[0].toText());
+ EXPECT_EQ("10.1.1.203", client.config_.dns_servers_[1].toText());
+ // Make sure that the Quotes Servers option has been received.
+ ASSERT_EQ(2, client.config_.quotes_servers_.size());
+ EXPECT_EQ("10.0.0.206", client.config_.quotes_servers_[0].toText());
+ EXPECT_EQ("10.0.0.207", client.config_.quotes_servers_[1].toText());
+ // Make sure that the Log Servers option has been received.
+ ASSERT_EQ(2, client.config_.log_servers_.size());
+ EXPECT_EQ("10.0.0.204", client.config_.log_servers_[0].toText());
+ EXPECT_EQ("10.0.0.205", client.config_.log_servers_[1].toText());
+}
+
+void
+HostOptionsTest::testHostOnlyOptions(const bool stateless) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+ client.requestOptions(DHO_COOKIE_SERVERS);
+
+ // Configure DHCP server.
+ configure(HOST_CONFIGS[2], *client.getServer());
+
+ if (stateless) {
+ // Need to relay the message from a specific address which can
+ // be matched with a configured subnet.
+ client.useRelay(true, IOAddress("10.0.0.233"));
+ ASSERT_NO_THROW(client.doInform());
+
+ } else {
+ // Perform 4-way exchange with the server but to not request any
+ // specific address in the DHCPDISCOVER message.
+ ASSERT_NO_THROW(client.doDORA());
+ }
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ if (!stateless) {
+ // Make sure that the client has got the lease for the reserved
+ // address.
+ ASSERT_EQ("10.0.0.7", client.config_.lease_.addr_.toText());
+ }
+
+ // Make sure that the Routers options has been received.
+ ASSERT_EQ(2, client.config_.routers_.size());
+ EXPECT_EQ("10.1.1.200", client.config_.routers_[0].toText());
+ EXPECT_EQ("10.1.1.201", client.config_.routers_[1].toText());
+ // Make sure that the Quotes Servers option has been received.
+ ASSERT_EQ(2, client.config_.quotes_servers_.size());
+ EXPECT_EQ("10.1.1.206", client.config_.quotes_servers_[0].toText());
+ EXPECT_EQ("10.1.1.207", client.config_.quotes_servers_[1].toText());
+
+ // Other options are not configured and should not be delivered.
+ EXPECT_EQ(0, client.config_.dns_servers_.size());
+ EXPECT_EQ(0, client.config_.log_servers_.size());
+}
+
+void
+HostOptionsTest::testOverrideVendorOptions(const bool stateless) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+
+ // Client needs to include V-I Vendor Specific Information option
+ // to include ORO suboption, which the server will use to determine
+ // which suboptions should be returned to the client.
+ OptionVendorPtr opt_vendor(new OptionVendor(Option::V4,
+ VENDOR_ID_CABLE_LABS));
+ // Include ORO with TFTP servers suboption code being requested.
+ opt_vendor->addOption(OptionPtr(new OptionUint8(Option::V4, DOCSIS3_V4_ORO,
+ DOCSIS3_V4_TFTP_SERVERS)));
+ client.addExtraOption(opt_vendor);
+
+ // Configure DHCP server.
+ configure(HOST_CONFIGS[3], *client.getServer());
+
+ if (stateless) {
+ // Need to relay the message from a specific address which can
+ // be matched with a configured subnet.
+ client.useRelay(true, IOAddress("10.0.0.233"));
+ ASSERT_NO_THROW(client.doInform());
+
+ } else {
+ // Perform 4-way exchange with the server.
+ ASSERT_NO_THROW(client.doDORA());
+ }
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ if (!stateless) {
+ // Make sure that the client has got the lease for the reserved
+ // address.
+ ASSERT_EQ("10.0.0.7", client.config_.lease_.addr_.toText());
+ }
+
+ // Make sure the server has responded with a V-I Vendor Specific
+ // Information option with exactly one suboption.
+ ASSERT_EQ(1, client.config_.vendor_suboptions_.size());
+ // Assume this suboption is a TFTP servers suboption.
+ std::multimap<unsigned int, OptionPtr>::const_iterator opt =
+ client.config_.vendor_suboptions_.find(DOCSIS3_V4_TFTP_SERVERS);
+ ASSERT_TRUE(opt->second);
+ Option4AddrLstPtr opt_tftp = boost::dynamic_pointer_cast<
+ Option4AddrLst>(opt->second);
+ ASSERT_TRUE(opt_tftp);
+ // TFTP servers suboption should contain addresses specified on host level.
+ const Option4AddrLst::AddressContainer& tftps = opt_tftp->getAddresses();
+ ASSERT_EQ(2, tftps.size());
+ EXPECT_EQ("10.1.1.202", tftps[0].toText());
+ EXPECT_EQ("10.1.1.203", tftps[1].toText());
+}
+
+// This test checks that host specific options override subnet specific
+// options. Overridden options are requested with Parameter Request List
+// option (stateless case).
+TEST_F(HostOptionsTest, overrideRequestedOptionsStateless) {
+ testOverrideRequestedOptions(STATELESS);
+}
+
+// This test checks that host specific options override subnet specific
+// options. Overridden options are requested with Parameter Request List
+// option (stateful case).
+TEST_F(HostOptionsTest, overrideRequestedOptionsStateful) {
+ testOverrideRequestedOptions(STATEFUL);
+}
+
+// This test checks that host specific options override subnet specific
+// options. Overridden options are the options which server sends
+// regardless if they are requested with Parameter Request List option
+// or not (stateless case).
+TEST_F(HostOptionsTest, overrideDefaultOptionsStateless) {
+ testOverrideDefaultOptions(STATELESS);
+}
+
+// This test checks that host specific options override subnet specific
+// options. Overridden options are the options which server sends
+// regardless if they are requested with Parameter Request List option
+// or not (stateful case).
+TEST_F(HostOptionsTest, overrideDefaultOptionsStateful) {
+ testOverrideDefaultOptions(STATEFUL);
+}
+
+// This test checks that client receives options when they are
+// solely defined in the host scope and not in the global or subnet
+// scope (stateless case).
+TEST_F(HostOptionsTest, hostOnlyOptionsStateless) {
+ testHostOnlyOptions(STATELESS);
+}
+
+// This test checks that client receives options when they are
+// solely defined in the host scope and not in the global or subnet
+// scope (stateful case).
+TEST_F(HostOptionsTest, hostOnlyOptionsStateful) {
+ testHostOnlyOptions(STATEFUL);
+}
+
+// This test checks that host specific vendor options override vendor
+// options defined in the global scope (stateless case).
+TEST_F(HostOptionsTest, overrideVendorOptionsStateless) {
+ testOverrideVendorOptions(STATELESS);
+}
+
+// This test checks that host specific vendor options override vendor
+// options defined in the global scope (stateful case).
+TEST_F(HostOptionsTest, overrideVendorOptionsStateful) {
+ testOverrideVendorOptions(STATEFUL);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/host_unittest.cc b/src/bin/dhcp4/tests/host_unittest.cc
new file mode 100644
index 0000000..b6774d0
--- /dev/null
+++ b/src/bin/dhcp4/tests/host_unittest.cc
@@ -0,0 +1,828 @@
+// Copyright (C) 2018-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 <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp4/tests/dhcp4_client.h>
+#include <boost/shared_ptr.hpp>
+#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 configuration(s) used throughout the Host tests.
+///
+/// - Configuration 0:
+/// - Used for testing global host reservations
+/// - 5 global reservations
+/// - 1 subnet: 10.0.0.0/24
+const char* CONFIGS[] = {
+ // Configuration 0
+ // 1 subnet, global only,
+ // global reservations for different identifier types
+ "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\n"
+ "\"host-reservation-identifiers\": [ \"circuit-id\", \"hw-address\",\n"
+ " \"duid\", \"client-id\" ],\n"
+ "\"reservations\": [ \n"
+ "{\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\",\n"
+ " \"hostname\": \"hw-host-dynamic\"\n"
+ "},\n"
+ "{\n"
+ " \"hw-address\": \"01:02:03:04:05:06\",\n"
+ " \"hostname\": \"hw-host-fixed\",\n"
+ " \"ip-address\": \"192.0.1.77\"\n"
+ "},\n"
+ "{\n"
+ " \"duid\": \"01:02:03:04:05\",\n"
+ " \"hostname\": \"duid-host\"\n"
+ "},\n"
+ "{\n"
+ " \"circuit-id\": \"'charter950'\",\n"
+ " \"hostname\": \"circuit-id-host\"\n"
+ "},\n"
+ "{\n"
+ " \"client-id\": \"01:11:22:33:44:55:66\",\n"
+ " \"hostname\": \"client-id-host\"\n"
+ "}\n"
+ "],\n"
+ "\"valid-lifetime\": 600,\n"
+ "\"subnet4\": [ { \n"
+ " \"subnet\": \"10.0.0.0/24\",\n"
+ " \"reservations-global\": true,\n"
+ " \"reservations-in-subnet\": false,\n"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ]\n"
+ "} ]\n"
+ "}\n"
+ ,
+ // Configuration 1 global vs in-subnet
+ // 2 subnets, one default reservations flags (aka in-subnet),
+ // one reservations flags global only
+ // Host reservations for the same client, one global, one in each subnet
+ "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\n"
+ "\"valid-lifetime\": 600,\n"
+ "\"reservations\": [ \n"
+ "{\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\",\n"
+ " \"hostname\": \"global-host\"\n"
+ "}\n"
+ "],\n"
+ "\"subnet4\": [\n"
+ " {\n"
+ " \"subnet\": \"10.0.0.0/24\", \n"
+ " \"id\": 10,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],\n"
+ " \"interface\": \"eth0\",\n"
+ " \"reservations\": [ \n"
+ " {\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\",\n"
+ " \"hostname\": \"subnet-10-host\"\n"
+ " }]\n"
+ " },\n"
+ " {\n"
+ " \"subnet\": \"192.0.2.0/26\", \n"
+ " \"id\": 20,"
+ " \"pools\": [ { \"pool\": \"192.0.2.10-192.0.2.63\" } ],\n"
+ " \"interface\": \"eth1\",\n"
+ " \"reservations-global\": true,\n"
+ " \"reservations-in-subnet\": false,\n"
+ " \"reservations\": [ \n"
+ " {\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\",\n"
+ " \"hostname\": \"subnet-20-host\"\n"
+ " }]\n"
+ " }\n"
+ "]\n"
+ "}\n"
+ ,
+ // Configuration 2 global and in-subnet with out-of-pool
+ "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\n"
+ "\"valid-lifetime\": 600,\n"
+ "\"reservations\": [ \n"
+ "{\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\",\n"
+ " \"hostname\": \"global-host\"\n"
+ "}\n"
+ "],\n"
+ "\"subnet4\": [\n"
+ " {\n"
+ " \"subnet\": \"10.0.0.0/24\", \n"
+ " \"id\": 10,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],\n"
+ " \"interface\": \"eth0\",\n"
+ " \"reservations-global\": false,\n"
+ " \"reservations-in-subnet\": true,\n"
+ " \"reservations-out-of-pool\": true,\n"
+ " \"reservations\": [ \n"
+ " {\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\",\n"
+ " \"hostname\": \"subnet-10-host\",\n"
+ " \"ip-address\": \"10.0.0.105\"\n"
+ " }]\n"
+ " }\n"
+ "]\n"
+ "}\n"
+ ,
+ // Configuration 3 global and in-subnet
+ "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\n"
+ "\"valid-lifetime\": 600,\n"
+ "\"reservations\": [ \n"
+ "{\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\",\n"
+ " \"hostname\": \"global-host\"\n"
+ "}\n"
+ "],\n"
+ "\"subnet4\": [\n"
+ " {\n"
+ " \"subnet\": \"10.0.0.0/24\", \n"
+ " \"id\": 10,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],\n"
+ " \"interface\": \"eth0\",\n"
+ " \"reservations-global\": false,\n"
+ " \"reservations-in-subnet\": true,\n"
+ " \"reservations-out-of-pool\": false,\n"
+ " \"reservations\": [ \n"
+ " {\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\",\n"
+ " \"hostname\": \"subnet-10-host\",\n"
+ " \"ip-address\": \"10.0.0.105\"\n"
+ " }]\n"
+ " }\n"
+ "]\n"
+ "}\n"
+ ,
+
+ // Configuration 4 client-class reservation in global, shared network
+ // 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"
+ "\"reservations-global\": true,\n"
+ "\"reservations-in-subnet\": false,\n"
+ "\"valid-lifetime\": 600,\n"
+ "\"reservations\": [ \n"
+ "{\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:fe\",\n"
+ " \"client-classes\": [ \"reserved_class\" ]\n"
+ "}\n"
+ "],\n"
+ "\"shared-networks\": [{"
+ " \"name\": \"frog\",\n"
+ " \"subnet4\": [\n"
+ " {\n"
+ " \"subnet\": \"10.0.0.0/24\", \n"
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.10-10.0.0.11\","
+ " \"client-class\": \"reserved_class\""
+ " }"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " },\n"
+ " {\n"
+ " \"subnet\": \"192.0.3.0/24\", \n"
+ " \"id\": 11,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.3.10-192.0.3.11\","
+ " \"client-class\": \"unreserved_class\""
+ " }"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " }\n"
+ " ]\n"
+ "}]\n"
+ "}",
+
+ // Configuration 5 client-class reservation in global, shared network
+ // and client-class guarded subnets.
+ "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\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\": 600,\n"
+ "\"reservations\": [ \n"
+ "{\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:fe\",\n"
+ " \"client-classes\": [ \"reserved_class\" ]\n"
+ "}\n"
+ "],\n"
+ "\"shared-networks\": [{"
+ " \"name\": \"frog\",\n"
+ " \"subnet4\": [\n"
+ " {\n"
+ " \"subnet\": \"10.0.0.0/24\", \n"
+ " \"id\": 10,"
+ " \"client-class\": \"reserved_class\","
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.10-10.0.0.10\""
+ " }"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " },\n"
+ " {\n"
+ " \"subnet\": \"192.0.3.0/24\", \n"
+ " \"id\": 11,"
+ " \"client-class\": \"unreserved_class\","
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.3.10-192.0.3.10\""
+ " }"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " }\n"
+ " ]\n"
+ "}]\n"
+ "}",
+
+ // Configuration 6 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\": 600,\n"
+ "\"subnet4\": [\n"
+ " {\n"
+ " \"subnet\": \"10.0.0.0/24\", \n"
+ " \"id\": 10,"
+ " \"reservations\": [{ \n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:fe\",\n"
+ " \"client-classes\": [ \"reserved_class\" ]\n"
+ " }],\n"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.10-10.0.0.11\","
+ " \"client-class\": \"reserved_class\""
+ " },"
+ " {"
+ " \"pool\": \"10.0.0.20-10.0.0.21\","
+ " \"client-class\": \"unreserved_class\""
+ " }"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " }\n"
+ "]\n"
+ "}",
+
+ // Configuration 7 multiple reservations for the same IP address.
+ "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\n"
+ "\"valid-lifetime\": 600,\n"
+ "\"ip-reservations-unique\": false,\n"
+ "\"host-reservation-identifiers\": [ \"hw-address\" ],\n"
+ "\"subnet4\": [\n"
+ " {\n"
+ " \"subnet\": \"10.0.0.0/24\",\n"
+ " \"id\": 10,\n"
+ " \"reservations\": [\n"
+ " { \n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:fe\",\n"
+ " \"ip-address\": \"10.0.0.123\"\n"
+ " },\n"
+ " { \n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\",\n"
+ " \"ip-address\": \"10.0.0.123\"\n"
+ " }\n"
+ " ],\n"
+ " \"pools\": [\n"
+ " {\n"
+ " \"pool\": \"10.0.0.10-10.0.0.255\"\n"
+ " }\n"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " }\n"
+ "]\n"
+ "}",
+
+ // Configuration 8 both global and in-subnet
+ // 2 subnets, one default reservations flags (aka in-subnet),
+ // one reservations flags global and in-subnet.
+ // Host reservations for the same client, one global, one in each subnet
+ "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\n"
+ "\"valid-lifetime\": 600,\n"
+ "\"reservations\": [ \n"
+ "{\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\",\n"
+ " \"hostname\": \"global-host\"\n"
+ "}\n"
+ "],\n"
+ "\"subnet4\": [\n"
+ " {\n"
+ " \"subnet\": \"10.0.0.0/24\", \n"
+ " \"id\": 10,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],\n"
+ " \"interface\": \"eth0\",\n"
+ " \"reservations\": [ \n"
+ " {\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\",\n"
+ " \"hostname\": \"subnet-10-host\"\n"
+ " }]\n"
+ " },\n"
+ " {\n"
+ " \"subnet\": \"192.0.2.0/26\", \n"
+ " \"id\": 20,"
+ " \"pools\": [ { \"pool\": \"192.0.2.10-192.0.2.63\" } ],\n"
+ " \"interface\": \"eth1\",\n"
+ " \"reservations-global\": true,\n"
+ " \"reservations-in-subnet\": true,\n"
+ " \"reservations-out-of-pool\": false,\n"
+ " \"reservations\": [ \n"
+ " {\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\",\n"
+ " \"hostname\": \"subnet-20-host\"\n"
+ " }]\n"
+ " }\n"
+ "]\n"
+ "}\n"
+};
+
+/// @brief Test fixture class for testing global v4 reservations.
+class HostTest : public Dhcpv4SrvTest {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets up fake interfaces.
+ HostTest()
+ : Dhcpv4SrvTest(),
+ iface_mgr_test_config_(true) {
+ // Let's wipe all existing statistics.
+ isc::stats::StatsMgr::instance().removeAll();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Cleans up statistics after the test.
+ ~HostTest() {
+ // Let's wipe all existing statistics.
+ isc::stats::StatsMgr::instance().removeAll();
+ }
+
+ /// @brief Interface Manager's fake configuration control.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+
+ /// @brief Conducts DORA exchange and checks assigned address and hostname
+ ///
+ /// If expected_host is empty, the test expects the hostname option to not
+ /// be assigned.
+ ///
+ /// @param config configuration to be used
+ /// @param client reference to a client instance
+ /// @param expected_host expected hostname to be assigned (may be empty)
+ /// @param expected_addr expected address to be assigned
+ void runDoraTest(const std::string& config, Dhcp4Client& client,
+ const std::string& expected_host,
+ const std::string& expected_addr,
+ const std::string& requested_addr = "") {
+
+ // Configure DHCP server.
+ ASSERT_NO_FATAL_FAILURE(configure(config, *client.getServer()));
+ client.requestOptions(DHO_HOST_NAME);
+
+ // Perform 4-way exchange with the server but to not request any
+ // specific address in the DHCPDISCOVER message.
+ boost::shared_ptr<IOAddress> hint;
+ if (!requested_addr.empty()) {
+ hint = boost::make_shared<IOAddress>(requested_addr);
+ }
+ ASSERT_NO_THROW(client.doDORA(hint));
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Fetch the hostname option
+ OptionStringPtr hostname = boost::dynamic_pointer_cast<
+ OptionString>(resp->getOption(DHO_HOST_NAME));
+
+ if (expected_host.empty()) {
+ ASSERT_FALSE(hostname);
+ } else {
+ ASSERT_TRUE(hostname);
+ EXPECT_EQ(expected_host, hostname->getValue());
+ }
+
+ EXPECT_EQ(client.config_.lease_.addr_.toText(), expected_addr);
+ }
+
+ /// @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 = "10.0.0.10",
+ const std::string& second_address = "192.0.3.10") {
+ Dhcp4Client client_resrv(Dhcp4Client::SELECTING);
+
+ // Use HW address for which we have host reservation including
+ // client class.
+ client_resrv.setHWAddress("aa:bb:cc:dd:ee:fe");
+ client_resrv.setIfaceName("eth0");
+ client_resrv.setIfaceIndex(ETH0_INDEX);
+
+ ASSERT_NO_FATAL_FAILURE(configure(CONFIGS[config_idx], *client_resrv.getServer()));
+
+ // This client should be given an address from the 10.0.0.0/24 pool.
+ // Let's use the 192.0.3.10 as a hint to make sure that the server
+ // refuses allocating it and uses the sole pool available for this
+ // client.
+ ASSERT_NO_THROW(client_resrv.doDORA(boost::make_shared<IOAddress>(second_address)));
+ ASSERT_TRUE(client_resrv.getContext().response_);
+ auto resp = client_resrv.getContext().response_;
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ EXPECT_EQ(first_address, resp->getYiaddr().toText());
+
+ // This client has no reservation and therefore should be
+ // assigned to the unreserved_class and be given an address
+ // from the other pool.
+ Dhcp4Client client_no_resrv(client_resrv.getServer(), Dhcp4Client::SELECTING);
+ client_no_resrv.setHWAddress("aa:bb:cc:dd:ee:ff");
+ client_no_resrv.setIfaceName("eth0");
+ client_no_resrv.setIfaceIndex(ETH0_INDEX);
+
+ // Let's use the address of 10.0.0.10 as a hint to make sure that the
+ // server refuses it in favor of the 192.0.3.10.
+ ASSERT_NO_THROW(client_no_resrv.doDORA(boost::make_shared<IOAddress>(first_address)));
+ ASSERT_TRUE(client_no_resrv.getContext().response_);
+ resp = client_no_resrv.getContext().response_;
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ EXPECT_EQ(second_address, resp->getYiaddr().toText());
+ }
+
+ /// @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 DHCPDICOVER 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 DHCPDISCOVER.
+ /// 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. In order to verify
+ /// that the allocation engine must fetch all reservations for the
+ /// reserved address and verifies that one of them belongs to the
+ /// second client.
+ ///
+ /// @param hw_address1 Hardware address of the first client having
+ /// the reservation.
+ /// @param hw_address2 Hardware address of the second client having
+ /// the reservation.
+ void testMultipleClientsRace(const std::string& hw_address1,
+ const std::string& hw_address2) {
+ // Create first client having the reservation.
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ client1.setHWAddress(hw_address1);
+
+ // Configure the server.
+ ASSERT_NO_FATAL_FAILURE(configure(CONFIGS[7], *client1.getServer()));
+
+ // Sends DHCPDISCOVER and make sure the client is offered the
+ // reserved IP address.
+ client1.doDiscover(boost::make_shared<IOAddress>("10.0.0.123"));
+ ASSERT_TRUE(client1.getContext().response_);
+ Pkt4Ptr resp = client1.getContext().response_;
+ ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+ EXPECT_EQ("10.0.0.123", resp->getYiaddr().toText());
+
+ // Create the second client matching the second reservation for
+ // the given IP address.
+ Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+ client2.setHWAddress(hw_address2);
+
+ // Make sure that the second client gets the reserved lease.
+ client2.doDORA(boost::make_shared<IOAddress>("10.0.0.123"));
+ ASSERT_TRUE(client2.getContext().response_);
+ resp = client2.getContext().response_;
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ EXPECT_EQ("10.0.0.123", resp->getYiaddr().toText());
+ }
+};
+
+// Verifies that a client, which fails to match to a global
+// reservation, still gets a dynamic address when subnet reservations
+// flags are global only.
+TEST_F(HostTest, globalHardwareNoMatch) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ client.setHWAddress("99:99:99:99:99:99");
+ runDoraTest(CONFIGS[0], client, "", "10.0.0.10");
+}
+
+// Verifies that a client, that matches to a global hostname
+// reservation, gets both the hostname and a dynamic address,
+// when the subnet reservations flags are global only.
+TEST_F(HostTest, globalHardwareDynamicAddress) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+ runDoraTest(CONFIGS[0], client, "hw-host-dynamic", "10.0.0.10");
+}
+
+// Verifies that a client matched to a global address reservation
+// gets both the hostname and the reserved address
+// when the subnet reservations flags are global only.
+TEST_F(HostTest, globalHardwareFixedAddress) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ //client.includeClientId(clientid_a);
+ client.setHWAddress("01:02:03:04:05:06");
+ runDoraTest(CONFIGS[0], client, "hw-host-fixed", "192.0.1.77");
+}
+
+// Verifies that a client can be matched to a global reservation by DUID
+TEST_F(HostTest, globalDuid) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Set hw address to a none-matching value
+ client.setHWAddress("99:99:99:99:99:99");
+
+ // - FF is a client identifier type for DUID,
+ // - 45454545 - represents 4 bytes for IAID
+ // - 01:02:03:04:05 - is an actual DUID for which there is a
+ client.includeClientId("FF:45:45:45:45:01:02:03:04:05");
+
+ runDoraTest(CONFIGS[0], client, "duid-host", "10.0.0.10");
+}
+
+// Verifies that a client can be matched to a global reservation by circuit-id
+TEST_F(HostTest, globalCircuitId) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Set hw address to a none-matching value
+ client.setHWAddress("99:99:99:99:99:99");
+
+ // Use relay agent so as the circuit-id can be inserted.
+ client.useRelay(true, IOAddress("10.0.0.1"), IOAddress("10.0.0.2"));
+
+ // Set the circuit id
+ client.setCircuitId("charter950");
+
+ runDoraTest(CONFIGS[0], client, "circuit-id-host", "10.0.0.10");
+}
+
+// Verifies that a client can be matched to a global reservation by client-id
+TEST_F(HostTest, globalClientID) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Set hw address to a none-matching value
+ client.setHWAddress("99:99:99:99:99:99");
+
+ // - 01 is a client identifier type for CLIENT_ID,
+ // - 11:22:33:44:55:66 - is an actual DUID for which there is a
+ client.includeClientId("01:11:22:33:44:55:66");
+
+ runDoraTest(CONFIGS[0], client, "client-id-host", "10.0.0.10");
+}
+
+// Verifies that even when a matching global reservation exists,
+// client will get a subnet scoped reservation, when subnet
+// reservations flags are default
+TEST_F(HostTest, defaultOverGlobal) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Hardware address matches all reservations
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+
+ // Subnet 10 uses default reservations flags (i.e. in-subnet), so its
+ // reservation should be used, rather than global.
+ runDoraTest(CONFIGS[1], client, "subnet-10-host", "10.0.0.10");
+}
+
+// Verifies that when there are matching reservations at
+// both the global and subnet levels, client will be matched
+// to the global reservation, when subnet reservations flags
+// are global only.
+TEST_F(HostTest, globalOverSubnet) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Hardware address matches all reservations
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+
+ // Change to subnet 20
+ client.setIfaceName("eth1");
+ client.setIfaceIndex(ETH1_INDEX);
+
+ // Subnet 20 uses global only reservations flags, so the global
+ // reservation should be used, rather than the subnet one.
+ runDoraTest(CONFIGS[1], client, "global-host", "192.0.2.10");
+}
+
+// Verifies that when there are matching reservations at
+// both the global and subnet levels, client will be matched
+// to the subnet reservation, when subnet reservations flags
+// are in-subnet and out-of-pool.
+TEST_F(HostTest, outOfPoolOverGlobal) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Hardware address matches all reservations
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+
+ // Subnet 10 uses in-subnet and out-of-pool reservations flags,
+ // so its reservation should be used, rather than global.
+ runDoraTest(CONFIGS[2], client, "subnet-10-host", "10.0.0.105");
+}
+
+// Verifies that when there are matching reservations at
+// both the global and subnet levels, client will be matched
+// to the subnet reservation, when subnet reservations flags
+// are in-subnet only.
+TEST_F(HostTest, allOverGlobal) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Hardware address matches all reservations
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+
+ // Subnet 10 uses default reservations flags (i.e. in-subnet), so its
+ // reservation should be used, rather than global.
+ runDoraTest(CONFIGS[3], client, "subnet-10-host", "10.0.0.105");
+}
+
+// Verifies that when there are matching reservations at
+// both the global and subnet levels, client will be matched
+// to the subnet reservation, when subnet reservations flags
+// are global and in-subnet, i.e. the subnet has the preference.
+TEST_F(HostTest, subnetOverGlobal) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Hardware address matches all reservations
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+
+ // Change to subnet 20
+ client.setIfaceName("eth1");
+ client.setIfaceIndex(ETH1_INDEX);
+
+ // Subnet 20 uses both global and in-subnet reservations flags,
+ // so the subnet reservation has the preference.
+ runDoraTest(CONFIGS[8], client, "subnet-20-host", "192.0.2.10");
+}
+
+// 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(4));
+}
+
+// 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(5));
+}
+
+// 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(6, "10.0.0.10", "10.0.0.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 MAC address matching the reservation.
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ client1.setHWAddress("aa:bb:cc:dd:ee:fe");
+ // Do 4-way exchange for this client to get the reserved address.
+ runDoraTest(CONFIGS[7], client1, "", "10.0.0.123");
+
+ // Create another client that has a reservation for the same
+ // IP address.
+ Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+ client2.setHWAddress("aa:bb:cc:dd:ee:ff");
+ // Do 4-way exchange with client2.
+ ASSERT_NO_THROW(client2.doDORA());
+
+ // Make sure that the server responded with DHCPACK.
+ ASSERT_TRUE(client2.getContext().response_);
+ Pkt4Ptr resp = client2.getContext().response_;
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Even though the client has reservation for this address the
+ // server should not assign this address because another client
+ // has taken it already.
+ EXPECT_NE("10.0.0.123", resp->getYiaddr().toText());
+ // Ensure stats are being recorded for HR conflicts
+ ObservationPtr subnet_conflicts = StatsMgr::instance().getObservation(
+ "subnet[10].v4-reservation-conflicts");
+ ASSERT_TRUE(subnet_conflicts);
+ ASSERT_EQ(1, subnet_conflicts->getInteger().first);
+ subnet_conflicts = StatsMgr::instance().getObservation("v4-reservation-conflicts");
+ ASSERT_TRUE(subnet_conflicts);
+ ASSERT_EQ(1, subnet_conflicts->getInteger().first);
+
+ // If the client1 releases the reserved lease, the client2 should acquire it.
+ ASSERT_NO_THROW(client1.doRelease());
+
+ // Client2 attempts to renew the currently used lease, but should get the
+ // DHCPNAK.
+ client2.setState(Dhcp4Client::RENEWING);
+ ASSERT_NO_THROW(client2.doRequest());
+ resp = client2.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPNAK, static_cast<int>(resp->getType()));
+
+ // The client falls back to 4-way exchange and gets the reserved address.
+ client2.setState(Dhcp4Client::SELECTING);
+ ASSERT_NO_THROW(client2.doDORA());
+ resp = client2.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ EXPECT_EQ("10.0.0.123", resp->getYiaddr().toText());
+}
+
+// This test verifies the case when two clients have reservations for
+// the same IP address. The first client sends DHCPDICOVER 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 DHCPDISCOVER.
+// 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. In order to verify
+// that the allocation engine must fetch all reservations for the
+// reserved address and verifies that one of them belongs to the
+// second client.
+TEST_F(HostTest, multipleClientsRace1) {
+ ASSERT_NO_FATAL_FAILURE(testMultipleClientsRace("aa:bb:cc:dd:ee:fe",
+ "aa:bb:cc:dd:ee:ff"));
+}
+
+// This is a second variant of the multipleClientsRace1. The test is almost
+// the same but the client matching the second reservation sends DHCPDISCOVER
+// 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("aa:bb:cc:dd:ee:ff",
+ "aa:bb:cc:dd:ee:fe"));
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/inform_unittest.cc b/src/bin/dhcp4/tests/inform_unittest.cc
new file mode 100644
index 0000000..07bd35d
--- /dev/null
+++ b/src/bin/dhcp4/tests/inform_unittest.cc
@@ -0,0 +1,653 @@
+// Copyright (C) 2014-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 <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp4/tests/dhcp4_client.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;
+
+namespace {
+
+/// @brief Set of JSON configurations used throughout the Inform tests.
+///
+/// - Configuration 0:
+/// - Used for testing direct traffic
+/// - 1 subnet: 10.0.0.0/24
+/// - 1 pool: 10.0.0.10-10.0.0.100
+/// - Router option present: 10.0.0.200 and 10.0.0.201
+/// - Domain Name Server option present: 10.0.0.202, 10.0.0.203.
+/// - Log Servers option present: 192.0.2.200 and 192.0.2.201
+/// - Quotes Servers option present: 192.0.2.202, 192.0.2.203.
+///
+/// - Configuration 1:
+/// - Use for testing relayed messages
+/// - 1 subnet: 192.0.2.0/24
+/// - Router option present: 192.0.2.200 and 192.0.2.201
+/// - Domain Name Server option present: 192.0.2.202, 192.0.2.203.
+/// - Log Servers option present: 192.0.2.200 and 192.0.2.201
+/// - Quotes Servers option present: 192.0.2.202, 192.0.2.203.
+///
+/// - Configuration 2:
+/// - This configuration provides reservations for next-server,
+/// server-hostname and boot-file-name value.
+/// - 1 subnet: 192.0.2.0/24
+/// - 1 reservation for this subnet:
+/// - Client's HW address: aa:bb:cc:dd:ee:ff
+/// - next-server = 10.0.0.7
+/// - server name = "some-name.example.org"
+/// - boot-file-name = "bootfile.efi"
+///
+/// - Configuration 3:
+/// - This configuration provides reservations for big options
+/// server-hostname and boot-file-name value.
+/// - 1 subnet: 192.0.2.0/24
+/// - 1 reservation for this subnet:
+/// - Client's HW address: aa:bb:cc:dd:ee:ff
+/// - option 240 = data
+/// -00010203040506070809-00010203040506070809-00010203040506070809-00010203040506070809
+/// -00010203040506070809-00010203040506070809-00010203040506070809-00010203040506070809
+/// -00010203040506070809-00010203040506070809-00010203040506070809-00010203040506070809
+/// -data
+const char* INFORM_CONFIGS[] = {
+// Configuration 0
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"routers\","
+ " \"data\": \"10.0.0.200,10.0.0.201\""
+ " },"
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.202,10.0.0.203\""
+ " },"
+ " {"
+ " \"name\": \"log-servers\","
+ " \"data\": \"10.0.0.202,10.0.0.203\""
+ " },"
+ " {"
+ " \"name\": \"cookie-servers\","
+ " \"data\": \"10.0.0.200,10.0.0.201\""
+ " } ]"
+ " } ]"
+ "}",
+
+// Configuration 1
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"option-data\": [ {"
+ " \"name\": \"routers\","
+ " \"data\": \"192.0.2.200,192.0.2.201\""
+ " },"
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.202,192.0.2.203\""
+ " },"
+ " {"
+ " \"name\": \"log-servers\","
+ " \"data\": \"10.0.0.200,10.0.0.201\""
+ " },"
+ " {"
+ " \"name\": \"cookie-servers\","
+ " \"data\": \"10.0.0.202,10.0.0.203\""
+ " } ]"
+ " } ]"
+ "}",
+
+// Configuration 2
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"next-server\": \"10.0.0.1\","
+ "\"server-hostname\": \"nohost\","
+ "\"boot-file-name\": \"nofile\","
+ "\"subnet4\": [ { "
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"reservations\": [ "
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"next-server\": \"10.0.0.7\","
+ " \"server-hostname\": \"some-name.example.org\","
+ " \"boot-file-name\": \"bootfile.efi\""
+ " }"
+ " ]"
+ "} ]"
+ "}",
+
+// Configuration 3
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"next-server\": \"10.0.0.1\","
+ "\"server-hostname\": \"nohost\","
+ "\"boot-file-name\": \"nofile\","
+ "\"option-def\": ["
+ " {"
+ " \"array\": false,"
+ " \"code\": 240,"
+ " \"encapsulate\": \"\","
+ " \"name\": \"my-option\","
+ " \"space\": \"dhcp4\","
+ " \"type\": \"string\""
+ " }"
+ "],"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"reservations\": [ "
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"option-data\": ["
+ " {"
+ " \"always-send\": false,"
+ " \"code\": 240,"
+ " \"name\": \"my-option\","
+ " \"csv-format\": true,"
+ " \"data\": \"data"
+ "-00010203040506070809-00010203040506070809-00010203040506070809-00010203040506070809"
+ "-00010203040506070809-00010203040506070809-00010203040506070809-00010203040506070809"
+ "-00010203040506070809-00010203040506070809-00010203040506070809-00010203040506070809"
+ "-data\","
+ " \"space\": \"dhcp4\""
+ " }"
+ "],"
+ " }"
+ " ]"
+ "} ]"
+ "}",
+};
+
+/// @brief Test fixture class for testing DHCPINFORM.
+class InformTest : public Dhcpv4SrvTest {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets up fake interfaces.
+ InformTest()
+ : Dhcpv4SrvTest(),
+ iface_mgr_test_config_(true) {
+ // Let's wipe all existing statistics.
+ isc::stats::StatsMgr::instance().removeAll();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Cleans up statistics after the test.
+ ~InformTest() {
+ // Let's wipe all existing statistics.
+ isc::stats::StatsMgr::instance().removeAll();
+ }
+
+ /// @brief Interface Manager's fake configuration control.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+
+};
+
+// Test that directly connected client's DHCPINFORM message is processed and
+// DHCPACK message is sent back.
+TEST_F(InformTest, directClientBroadcast) {
+ Dhcp4Client client;
+ // Configure DHCP server.
+ configure(INFORM_CONFIGS[0], *client.getServer());
+ // Request some configuration when DHCPINFORM is sent.
+ client.requestOptions(DHO_LOG_SERVERS, DHO_COOKIE_SERVERS);
+ // Preconfigure the client with the IP address.
+ client.createLease(IOAddress("10.0.0.56"), 600);
+
+ // Send DHCPINFORM message to the server.
+ ASSERT_NO_THROW(client.doInform());
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Response should have been unicast to the ciaddr.
+ EXPECT_EQ(IOAddress("10.0.0.56"), resp->getLocalAddr());
+ // The ciaddr should have been copied.
+ EXPECT_EQ(IOAddress("10.0.0.56"), resp->getCiaddr());
+ // Response must not be relayed.
+ EXPECT_FALSE(resp->isRelayed());
+ // Make sure that the server id is present.
+ EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+
+ // Make sure that the Routers option has been received.
+ ASSERT_EQ(2, client.config_.routers_.size());
+ EXPECT_EQ("10.0.0.200", client.config_.routers_[0].toText());
+ EXPECT_EQ("10.0.0.201", client.config_.routers_[1].toText());
+ // Make sure that the DNS Servers option has been received.
+ ASSERT_EQ(2, client.config_.dns_servers_.size());
+ EXPECT_EQ("10.0.0.202", client.config_.dns_servers_[0].toText());
+ EXPECT_EQ("10.0.0.203", client.config_.dns_servers_[1].toText());
+ // Make sure that the Log Servers option has been received.
+ ASSERT_EQ(2, client.config_.quotes_servers_.size());
+ EXPECT_EQ("10.0.0.200", client.config_.quotes_servers_[0].toText());
+ EXPECT_EQ("10.0.0.201", client.config_.quotes_servers_[1].toText());
+ // Make sure that the Quotes Servers option has been received.
+ ASSERT_EQ(2, client.config_.log_servers_.size());
+ EXPECT_EQ("10.0.0.202", client.config_.log_servers_[0].toText());
+ EXPECT_EQ("10.0.0.203", client.config_.log_servers_[1].toText());
+
+ // Check that we can send another DHCPINFORM message using
+ // different ciaddr and we will get the configuration.
+ client.createLease(IOAddress("10.0.0.12"), 600);
+ // This time do not request Quotes Servers option and it should not
+ // be returned.
+ client.requestOptions(DHO_LOG_SERVERS);
+
+ // Send DHCPINFORM.
+ ASSERT_NO_THROW(client.doInform());
+
+ // Make sure that the server responded.
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Response should have been unicast to the ciaddr.
+ EXPECT_EQ(IOAddress("10.0.0.12"), resp->getLocalAddr());
+ // The ciaddr should have been copied.
+ EXPECT_EQ(IOAddress("10.0.0.12"), resp->getCiaddr());
+ // Response must not be relayed.
+ EXPECT_FALSE(resp->isRelayed());
+
+ // Make sure that the server id is present.
+ EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+ // Make sure that the Routers option has been received.
+ ASSERT_EQ(2, client.config_.routers_.size());
+ EXPECT_EQ("10.0.0.200", client.config_.routers_[0].toText());
+ EXPECT_EQ("10.0.0.201", client.config_.routers_[1].toText());
+ // Make sure that the DNS Servers option has been received.
+ ASSERT_EQ(2, client.config_.dns_servers_.size());
+ EXPECT_EQ("10.0.0.202", client.config_.dns_servers_[0].toText());
+ EXPECT_EQ("10.0.0.203", client.config_.dns_servers_[1].toText());
+ // Make sure that the Quotes Servers option hasn't been received.
+ ASSERT_TRUE(client.config_.quotes_servers_.empty());
+}
+
+// This test checks that the server drops DHCPINFORM message when the
+// source address and ciaddr is 0.
+TEST_F(InformTest, directClientBroadcastNoAddress) {
+ Dhcp4Client client;
+ // Configure DHCP server.
+ configure(INFORM_CONFIGS[0], *client.getServer());
+ // Request some configuration when DHCPINFORM is sent.
+ client.requestOptions(DHO_LOG_SERVERS, DHO_COOKIE_SERVERS);
+ // Send DHCPINFORM message to the server.
+ ASSERT_NO_THROW(client.doInform());
+ // Make sure that the server dropped the message.
+ ASSERT_FALSE(client.getContext().response_);
+}
+
+// Test that client's DHCPINFORM message sent to a unicast address
+// is received and processed by the server and that the DHCPACK is
+// is sent.
+TEST_F(InformTest, directClientUnicast) {
+ Dhcp4Client client;
+ // Configure DHCP server.
+ configure(INFORM_CONFIGS[0], *client.getServer());
+ // Preconfigure the client with the IP address.
+ client.createLease(IOAddress("10.0.0.56"), 600);
+ // Set remote address to unicast.
+ client.setDestAddress(IOAddress("10.0.0.1"));
+ // Send DHCPINFORM message to the server.
+ ASSERT_NO_THROW(client.doInform());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Response should have been unicast to the ciaddr.
+ EXPECT_EQ(IOAddress("10.0.0.56"), resp->getLocalAddr());
+ // The ciaddr should have been copied.
+ EXPECT_EQ(IOAddress("10.0.0.56"), resp->getCiaddr());
+ // Response must not be relayed.
+ EXPECT_FALSE(resp->isRelayed());
+ // Make sure that the server id is present.
+ EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+ // Make sure that the Routers option has been received.
+ ASSERT_EQ(2, client.config_.routers_.size());
+ EXPECT_EQ("10.0.0.200", client.config_.routers_[0].toText());
+ EXPECT_EQ("10.0.0.201", client.config_.routers_[1].toText());
+ // Make sure that the DNS Servers option has been received.
+ ASSERT_EQ(2, client.config_.dns_servers_.size());
+ EXPECT_EQ("10.0.0.202", client.config_.dns_servers_[0].toText());
+ EXPECT_EQ("10.0.0.203", client.config_.dns_servers_[1].toText());
+}
+
+// This test checks that the server responds to the source address of the
+// packet received from the directly connected client if the client didn't
+// set the ciaddr.
+TEST_F(InformTest, directClientNoCiaddr) {
+ Dhcp4Client client;
+ // Configure DHCP server.
+ configure(INFORM_CONFIGS[0], *client.getServer());
+ // Preconfigure the client with the IP address.
+ client.createLease(IOAddress("10.0.0.56"), 600);
+ // Send DHCPINFORM message (with ciaddr not set) to the server.
+ ASSERT_NO_THROW(client.doInform(false));
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Response should have been unicast to the client address.
+ EXPECT_EQ(IOAddress("10.0.0.56"), resp->getLocalAddr());
+ // The ciaddr should be 0.
+ EXPECT_EQ(IOAddress("0.0.0.0"), resp->getCiaddr());
+ // Response must not be relayed.
+ EXPECT_FALSE(resp->isRelayed());
+ EXPECT_EQ(DHCP4_CLIENT_PORT, resp->getLocalPort());
+ EXPECT_EQ(DHCP4_SERVER_PORT, resp->getRemotePort());
+ // Make sure that the server id is present.
+ EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+ // Make sure that the Routers option has been received.
+ ASSERT_EQ(2, client.config_.routers_.size());
+ EXPECT_EQ("10.0.0.200", client.config_.routers_[0].toText());
+ EXPECT_EQ("10.0.0.201", client.config_.routers_[1].toText());
+ // Make sure that the DNS Servers option has been received.
+ ASSERT_EQ(2, client.config_.dns_servers_.size());
+ EXPECT_EQ("10.0.0.202", client.config_.dns_servers_[0].toText());
+ EXPECT_EQ("10.0.0.203", client.config_.dns_servers_[1].toText());
+}
+
+// This test checks that the server receiving DHCPINFORM via relay, unicasts the
+// DHCPACK to the client (ciaddr).
+TEST_F(InformTest, relayedClient) {
+ Dhcp4Client client;
+ // Configure DHCP server.
+ configure(INFORM_CONFIGS[1], *client.getServer());
+ // Message is relayed.
+ client.useRelay();
+ // Request some configuration when DHCPINFORM is sent.
+ client.requestOptions(DHO_LOG_SERVERS, DHO_COOKIE_SERVERS);
+ // Preconfigure the client with the IP address.
+ client.createLease(IOAddress("192.0.2.56"), 600);
+ // Send DHCPINFORM message to the server.
+ ASSERT_NO_THROW(client.doInform());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Response should have been unicast to the ciaddr.
+ EXPECT_EQ(IOAddress("192.0.2.56"), resp->getLocalAddr());
+ // The ciaddr should have been copied.
+ EXPECT_EQ(IOAddress("192.0.2.56"), resp->getCiaddr());
+ // Response is unicast to the client, so it must not be relayed.
+ EXPECT_FALSE(resp->isRelayed());
+ EXPECT_EQ(DHCP4_CLIENT_PORT, resp->getLocalPort());
+ EXPECT_EQ(DHCP4_SERVER_PORT, resp->getRemotePort());
+ // Make sure that the server id is present.
+ EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+ // Make sure that the Routers option has been received.
+ ASSERT_EQ(2, client.config_.routers_.size());
+ EXPECT_EQ("192.0.2.200", client.config_.routers_[0].toText());
+ EXPECT_EQ("192.0.2.201", client.config_.routers_[1].toText());
+ // Make sure that the DNS Servers option has been received.
+ ASSERT_EQ(2, client.config_.dns_servers_.size());
+ EXPECT_EQ("192.0.2.202", client.config_.dns_servers_[0].toText());
+ EXPECT_EQ("192.0.2.203", client.config_.dns_servers_[1].toText());
+ // Make sure that the Quotes Servers option has been received.
+ ASSERT_EQ(2, client.config_.quotes_servers_.size());
+ EXPECT_EQ("10.0.0.202", client.config_.quotes_servers_[0].toText());
+ EXPECT_EQ("10.0.0.203", client.config_.quotes_servers_[1].toText());
+ // Make sure that the Log Servers option has been received.
+ ASSERT_EQ(2, client.config_.log_servers_.size());
+ EXPECT_EQ("10.0.0.200", client.config_.log_servers_[0].toText());
+ EXPECT_EQ("10.0.0.201", client.config_.log_servers_[1].toText());
+}
+
+// This test checks that the server can respond to the DHCPINFORM message
+// received via relay when the ciaddr is not set.
+TEST_F(InformTest, relayedClientNoCiaddr) {
+ Dhcp4Client client;
+ // Configure DHCP server.
+ configure(INFORM_CONFIGS[1], *client.getServer());
+ // Message is relayed.
+ client.useRelay();
+ // Send DHCPINFORM message to the server.
+ ASSERT_NO_THROW(client.doInform());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Response should go through a relay as there is no ciaddr.
+ EXPECT_EQ(IOAddress("192.0.2.2"), resp->getLocalAddr());
+ EXPECT_EQ(IOAddress("192.0.2.2"), resp->getGiaddr());
+ EXPECT_EQ(1, resp->getHops());
+ EXPECT_EQ(DHCP4_SERVER_PORT, resp->getLocalPort());
+ EXPECT_EQ(DHCP4_SERVER_PORT, resp->getRemotePort());
+ // In the case when the client didn't set the ciaddr and the message
+ // was received via relay the server sets the Broadcast flag to help
+ // the relay forwarding the message (without yiaddr) to the client.
+ EXPECT_EQ(BOOTP_BROADCAST, resp->getFlags() & BOOTP_BROADCAST);
+ // Make sure that the server id is present.
+ EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+ // Make sure that the Routers option has been received.
+ ASSERT_EQ(2, client.config_.routers_.size());
+ EXPECT_EQ("192.0.2.200", client.config_.routers_[0].toText());
+ EXPECT_EQ("192.0.2.201", client.config_.routers_[1].toText());
+ // Make sure that the DNS Servers option has been received.
+ ASSERT_EQ(2, client.config_.dns_servers_.size());
+ EXPECT_EQ("192.0.2.202", client.config_.dns_servers_[0].toText());
+ EXPECT_EQ("192.0.2.203", client.config_.dns_servers_[1].toText());
+}
+
+// This test verifies that the server assigns reserved values for the
+// siaddr, sname and file fields carried within DHCPv4 message.
+TEST_F(InformTest, messageFieldsReservations) {
+ // Client has a reservation.
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Message is relayed.
+ client.useRelay();
+ // Set explicit HW address so as it matches the reservation in the
+ // configuration used below.
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+ // Configure DHCP server.
+ configure(INFORM_CONFIGS[2], *client.getServer());
+ // Client sends DHCPINFORM and should receive reserved fields.
+ ASSERT_NO_THROW(client.doInform());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Check that the reserved values have been assigned.
+ EXPECT_EQ("10.0.0.7", client.config_.siaddr_.toText());
+ EXPECT_EQ("some-name.example.org", client.config_.sname_);
+ EXPECT_EQ("bootfile.efi", client.config_.boot_file_name_);
+}
+
+// This test verifies that the server assigns and splits long options within
+// DHCPv4 message.
+TEST_F(InformTest, messageFieldsLongOptions) {
+ // Client has a reservation.
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Message is relayed.
+ client.useRelay();
+ // Set explicit HW address so as it matches the reservation in the
+ // configuration used below.
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+ // Configure DHCP server.
+ configure(INFORM_CONFIGS[3], *client.getServer());
+ // Client requests big option.
+ client.requestOption(240);
+ // Client also sends multiple options with the same code.
+ OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_AGENT_OPTIONS);
+ ASSERT_TRUE(rai_def);
+ // Create RAI options which should be fused by the server.
+ OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4));
+ for (uint8_t i = 0; i < 4; ++i) {
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ OptionBuffer buf_in(16);
+ for (uint8_t j = 0; j < 16; ++j) {
+ buf_in[j] = i * 16 + j;
+ }
+
+ OptionPtr circuit_id_opt(new Option(Option::V4,
+ RAI_OPTION_AGENT_CIRCUIT_ID, buf_in));
+ ASSERT_TRUE(circuit_id_opt);
+ rai->addOption(circuit_id_opt);
+ }
+ client.addExtraOption(rai);
+
+ // Client sends large options which should be split by the client.
+ OptionDefinition opt_def_bar("option-foo", 231, "my-space", "binary",
+ "option-foo-space");
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ OptionBuffer buf_in(2560);
+ for (uint32_t i = 0; i < 2560; ++i) {
+ buf_in[i] = i;
+ }
+
+ boost::shared_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(option.reset(new OptionCustom(opt_def_bar, Option::V4, buf_in)));
+ ASSERT_TRUE(option);
+ client.addExtraOption(option);
+ // Client sends DHCPINFORM and should receive reserved fields.
+ ASSERT_NO_THROW(client.doInform());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Long option should have been split by the client on pack, serialized and
+ // then restored.
+ uint32_t count = 0;
+ uint8_t index = 0;
+ for (auto const& option : client.getContext().query_->options_) {
+ if (option.first == 231) {
+ for (auto const& value : option.second->getData()) {
+ ASSERT_EQ(value, index);
+ index++;
+ }
+ count++;
+ }
+ }
+ ASSERT_EQ(1, count);
+
+ count = 0;
+ for (auto const& option : resp->options_) {
+ if (option.first == DHO_DHCP_AGENT_OPTIONS) {
+ for (auto const& suboption: option.second->getOptions()) {
+ if (suboption.first == RAI_OPTION_AGENT_CIRCUIT_ID) {
+ uint8_t index = 0;
+ for (auto const& value : suboption.second->getData()) {
+ ASSERT_EQ(value, index);
+ index++;
+ }
+ count++;
+ }
+ }
+ }
+ }
+ // Multiple options should have been fused by the server on unpack.
+ ASSERT_EQ(count, 1);
+
+ // Check that the reserved and requested values have been assigned.
+ string expected =
+ "-00010203040506070809-00010203040506070809-00010203040506070809-00010203040506070809"
+ "-00010203040506070809-00010203040506070809-00010203040506070809-00010203040506070809"
+ "-00010203040506070809-00010203040506070809-00010203040506070809-00010203040506070809";
+
+ count = 0;
+ string value = "";
+ for (auto const& option : resp->options_) {
+ if (option.second->getType() == 240) {
+ value += string(reinterpret_cast<const char*>(&option.second->getData()[0]),
+ option.second->getData().size());
+ count++;
+ }
+ }
+ // Multiple options should have been fused by the server on unpack.
+ ASSERT_EQ(count, 1);
+ ASSERT_EQ(value, string("data") + expected + string("-data"));
+}
+
+/// This test verifies that after a client completes its INFORM exchange,
+/// appropriate statistics are updated.
+TEST_F(InformTest, statisticsInform) {
+ Dhcp4Client client;
+ // Configure DHCP server.
+ configure(INFORM_CONFIGS[0], *client.getServer());
+ // Request some configuration when DHCPINFORM is sent.
+ client.requestOptions(DHO_LOG_SERVERS, DHO_COOKIE_SERVERS);
+ // Preconfigure the client with the IP address.
+ client.createLease(IOAddress("10.0.0.56"), 600);
+
+ // Send DHCPINFORM message to the server.
+ ASSERT_NO_THROW(client.doInform());
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Ok, let's check the statistics.
+ using namespace isc::stats;
+ StatsMgr& mgr = StatsMgr::instance();
+ ObservationPtr pkt4_received = mgr.getObservation("pkt4-received");
+ ObservationPtr pkt4_inform_received = mgr.getObservation("pkt4-inform-received");
+ ObservationPtr pkt4_ack_sent = mgr.getObservation("pkt4-ack-sent");
+ ObservationPtr pkt4_sent = mgr.getObservation("pkt4-sent");
+
+ // All expected statistics must be present.
+ ASSERT_TRUE(pkt4_received);
+ ASSERT_TRUE(pkt4_inform_received);
+ ASSERT_TRUE(pkt4_ack_sent);
+ ASSERT_TRUE(pkt4_sent);
+
+ // And they must have expected values.
+ EXPECT_EQ(1, pkt4_received->getInteger().first);
+ EXPECT_EQ(1, pkt4_inform_received->getInteger().first);
+ EXPECT_EQ(1, pkt4_ack_sent->getInteger().first);
+ EXPECT_EQ(1, pkt4_sent->getInteger().first);
+
+ // Let the client send iform 4 times, which should make the server
+ // to send 4 acks.
+ ASSERT_NO_THROW(client.doInform());
+ ASSERT_NO_THROW(client.doInform());
+ ASSERT_NO_THROW(client.doInform());
+ ASSERT_NO_THROW(client.doInform());
+
+ // Let's see if the stats are properly updated.
+ EXPECT_EQ(5, pkt4_received->getInteger().first);
+ EXPECT_EQ(5, pkt4_inform_received->getInteger().first);
+ EXPECT_EQ(5, pkt4_ack_sent->getInteger().first);
+ EXPECT_EQ(5, pkt4_sent->getInteger().first);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/kea_controller_unittest.cc b/src/bin/dhcp4/tests/kea_controller_unittest.cc
new file mode 100644
index 0000000..86d9d09
--- /dev/null
+++ b/src/bin/dhcp4/tests/kea_controller_unittest.cc
@@ -0,0 +1,1094 @@
+// Copyright (C) 2014-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 <asiolink/interval_timer.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <cc/command_interpreter.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/hwaddr.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/parser_context.h>
+#include <dhcp4/tests/dhcp4_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 <signal.h>
+#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 CBControlDHCPv4.
+///
+/// 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 TestCBControlDHCPv4 : public CBControlDHCPv4 {
+public:
+
+ /// @brief Constructor.
+ TestCBControlDHCPv4()
+ : CBControlDHCPv4(), 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 TestCBControlDHCPv4.
+typedef boost::shared_ptr<TestCBControlDHCPv4> TestCBControlDHCPv4Ptr;
+
+/// @brief "Naked" DHCPv4 server.
+///
+/// Exposes internal fields and installs stub implementation of the
+/// @c CBControlDHCPv4 object.
+class NakedControlledDhcpv4Srv: public ControlledDhcpv4Srv {
+public:
+
+ /// @brief Constructor.
+ NakedControlledDhcpv4Srv()
+ : ControlledDhcpv4Srv(0) {
+ // We're replacing the @c CBControlDHCPv4 instance with our
+ // stub implementation used in tests.
+ cb_control_.reset(new TestCBControlDHCPv4());
+ }
+};
+
+/// @brief test class for Kea configuration backend
+///
+/// This class is used for testing Kea configuration backend.
+/// It is very simple and currently focuses on reading
+/// config file from disk. It is expected to be expanded in the
+/// near future.
+class JSONFileBackendTest : public isc::dhcp::test::BaseServerTest {
+public:
+ JSONFileBackendTest() {
+ }
+
+ ~JSONFileBackendTest() {
+ LeaseMgrFactory::destroy();
+ static_cast<void>(remove(TEST_FILE));
+ static_cast<void>(remove(TEST_INCLUDE));
+ };
+
+ /// @brief writes specified content to a well known file
+ ///
+ /// Writes specified content to TEST_FILE. Tests will
+ /// attempt to read that file.
+ ///
+ /// @param file_name name of file to be written
+ /// @param content content to be written to file
+ 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);
+ bool stopped = false;
+ timer.setup([&io_service, &stopped]() {
+ io_service->stop();
+ stopped = true;
+ }, 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 <<
+ "{ \"Dhcp4\": {"
+ "\"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"
+ "\"subnet4\": [ ],"
+ "\"valid-lifetime\": 4000 }"
+ "}";
+ writeFile(TEST_FILE, config.str());
+
+ // Create an instance of the server and initialize it.
+ boost::scoped_ptr<NakedControlledDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedControlledDhcpv4Srv()));
+ ASSERT_NO_THROW(srv->init(TEST_FILE));
+
+ // Get the CBControlDHCPv4 object belonging to this server.
+ auto cb_control = boost::dynamic_pointer_cast<TestCBControlDHCPv4>(srv->getCBControl());
+
+ // Verify that the parameter passed to the databaseConfigFetch has an
+ // expected value.
+ cb_control->enableCheckFetchMode();
+
+ // Instruct our stub implementation of the CBControlDHCPv4 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 =
+ ControlledDhcpv4Srv::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());
+ }
+ }
+ }
+
+ /// Name of a config file used during tests
+ 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 = "{ \"Dhcp4\": {"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\" "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.3.101 - 192.0.3.150\" } ],"
+ " \"subnet\": \"192.0.3.0/24\", "
+ " \"id\": 0 "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
+ " \"subnet\": \"192.0.4.0/24\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+ "}";
+
+ writeFile(TEST_FILE, config);
+
+ // Now initialize the server
+ boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv4Srv(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 Subnet4Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->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("192.0.2.0", (*subnet)->get().first.toText());
+ EXPECT_EQ(24, (*subnet)->get().second);
+
+ // Check pools in the first subnet.
+ const PoolCollection& pools1 = (*subnet)->getPools(Lease::TYPE_V4);
+ ASSERT_EQ(1, pools1.size());
+ EXPECT_EQ("192.0.2.1", pools1.at(0)->getFirstAddress().toText());
+ EXPECT_EQ("192.0.2.100", pools1.at(0)->getLastAddress().toText());
+ EXPECT_EQ(Lease::TYPE_V4, pools1.at(0)->getType());
+
+ // Check subnet 2.
+ ++subnet;
+ ASSERT_TRUE(subnet != subnets->end());
+ EXPECT_EQ("192.0.3.0", (*subnet)->get().first.toText());
+ EXPECT_EQ(24, (*subnet)->get().second);
+
+ // Check pools in the second subnet.
+ const PoolCollection& pools2 = (*subnet)->getPools(Lease::TYPE_V4);
+ ASSERT_EQ(1, pools2.size());
+ EXPECT_EQ("192.0.3.101", pools2.at(0)->getFirstAddress().toText());
+ EXPECT_EQ("192.0.3.150", pools2.at(0)->getLastAddress().toText());
+ EXPECT_EQ(Lease::TYPE_V4, pools2.at(0)->getType());
+
+ // And finally check subnet 3.
+ ++subnet;
+ ASSERT_TRUE(subnet != subnets->end());
+ EXPECT_EQ("192.0.4.0", (*subnet)->get().first.toText());
+ EXPECT_EQ(24, (*subnet)->get().second);
+
+ // ... and its only pool.
+ const PoolCollection& pools3 = (*subnet)->getPools(Lease::TYPE_V4);
+ EXPECT_EQ("192.0.4.101", pools3.at(0)->getFirstAddress().toText());
+ EXPECT_EQ("192.0.4.150", pools3.at(0)->getLastAddress().toText());
+ EXPECT_EQ(Lease::TYPE_V4, 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"
+ "{ \"Dhcp4\": {"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, \n"
+ "# comments in the middle should be ignored, too\n"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.0/24\" } ],"
+ " \"subnet\": \"192.0.2.0/22\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+ "}";
+
+ writeFile(TEST_FILE, config_hash_comments);
+
+ // Now initialize the server
+ boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv4Srv(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 Subnet4Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(1, subnets->size());
+
+ // Check subnet 1.
+ auto subnet = subnets->begin();
+ ASSERT_TRUE(subnet != subnets->end());
+ EXPECT_EQ("192.0.2.0", (*subnet)->get().first.toText());
+ EXPECT_EQ(22, (*subnet)->get().second);
+
+ // Check pools in the first subnet.
+ const PoolCollection& pools1 = (*subnet)->getPools(Lease::TYPE_V4);
+ ASSERT_EQ(1, pools1.size());
+ EXPECT_EQ("192.0.2.0", pools1.at(0)->getFirstAddress().toText());
+ EXPECT_EQ("192.0.2.255", pools1.at(0)->getLastAddress().toText());
+ EXPECT_EQ(Lease::TYPE_V4, 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"
+ "{ \"Dhcp4\": {"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, \n"
+ "// comments in the middle should be ignored, too\n"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.0/24\" } ],"
+ " \"subnet\": \"192.0.2.0/22\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+ "}";
+
+ writeFile(TEST_FILE, config_cpp_line_comments);
+
+ // Now initialize the server
+ boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv4Srv(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 Subnet4Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(1, subnets->size());
+
+ // Check subnet 1.
+ auto subnet = subnets->begin();
+ ASSERT_TRUE(subnet != subnets->end());
+ EXPECT_EQ("192.0.2.0", (*subnet)->get().first.toText());
+ EXPECT_EQ(22, (*subnet)->get().second);
+
+ // Check pools in the first subnet.
+ const PoolCollection& pools1 = (*subnet)->getPools(Lease::TYPE_V4);
+ ASSERT_EQ(1, pools1.size());
+ EXPECT_EQ("192.0.2.0", pools1.at(0)->getFirstAddress().toText());
+ EXPECT_EQ("192.0.2.255", pools1.at(0)->getLastAddress().toText());
+ EXPECT_EQ(Lease::TYPE_V4, 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"
+ "{ \"Dhcp4\": {"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, \n"
+ "/* comments in the middle should be ignored, too*/\n"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.0/24\" } ],"
+ " \"subnet\": \"192.0.2.0/22\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+ "}";
+
+ writeFile(TEST_FILE, config_c_block_comments);
+
+ // Now initialize the server
+ boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv4Srv(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 Subnet4Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(1, subnets->size());
+
+ // Check subnet 1.
+ auto subnet = subnets->begin();
+ ASSERT_TRUE(subnet != subnets->end());
+ EXPECT_EQ("192.0.2.0", (*subnet)->get().first.toText());
+ EXPECT_EQ(22, (*subnet)->get().second);
+
+ // Check pools in the first subnet.
+ const PoolCollection& pools1 = (*subnet)->getPools(Lease::TYPE_V4);
+ ASSERT_EQ(1, pools1.size());
+ EXPECT_EQ("192.0.2.0", pools1.at(0)->getFirstAddress().toText());
+ EXPECT_EQ("192.0.2.255", pools1.at(0)->getLastAddress().toText());
+ EXPECT_EQ(Lease::TYPE_V4, 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_hash_comments = "{ \"Dhcp4\": {"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, \n"
+ "<?include \"" + string(TEST_INCLUDE) + "\"?>,"
+ "\"valid-lifetime\": 4000 }"
+ "}";
+ string include = "\n"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.0/24\" } ],"
+ " \"subnet\": \"192.0.2.0/22\" "
+ " } ]\n";
+
+ writeFile(TEST_FILE, config_hash_comments);
+ writeFile(TEST_INCLUDE, include);
+
+ // Now initialize the server
+ boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv4Srv(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 Subnet4Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(1, subnets->size());
+
+ // Check subnet 1.
+ auto subnet = subnets->begin();
+ ASSERT_TRUE(subnet != subnets->end());
+ EXPECT_EQ("192.0.2.0", (*subnet)->get().first.toText());
+ EXPECT_EQ(22, (*subnet)->get().second);
+
+ // Check pools in the first subnet.
+ const PoolCollection& pools1 = (*subnet)->getPools(Lease::TYPE_V4);
+ ASSERT_EQ(1, pools1.size());
+ EXPECT_EQ("192.0.2.0", pools1.at(0)->getFirstAddress().toText());
+ EXPECT_EQ("192.0.2.255", pools1.at(0)->getLastAddress().toText());
+ EXPECT_EQ(Lease::TYPE_V4, pools1.at(0)->getType());
+}
+
+// This test checks if recursive include of a file is detected
+TEST_F(JSONFileBackendTest, recursiveInclude) {
+
+ string config_recursive_include = "{ \"Dhcp4\": {"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ <?include \"" + string(TEST_INCLUDE) + "\"?> ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, \n"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.0/24\" } ],"
+ " \"subnet\": \"192.0.2.0/22\" "
+ " } ],"
+ "\"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<ControlledDhcpv4Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv4Srv(0))
+ );
+
+ // And configure it using config with comments.
+ try {
+ srv->init(TEST_FILE);
+ FAIL() << "Expected Dhcp4ParseError 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 Dhcp4 element
+// - Config file that contains Dhcp4 but has a content error
+TEST_F(JSONFileBackendTest, configBroken) {
+
+ // Empty config is not allowed, because Dhcp4 element is missing
+ string config_empty = "";
+
+ // This config does not have mandatory Dhcp4 element
+ string config_v4 = "{ \"Dhcp6\": { \"interfaces\": [ \"*\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"2001:db8::/80\" ],"
+ " \"subnet\": \"2001:db8::/64\" "
+ " } ]}";
+
+ // This has Dhcp4 element, but it's utter nonsense
+ string config_nonsense = "{ \"Dhcp4\": { \"reviews\": \"are so much fun\" } }";
+
+ // Now initialize the server
+ boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv4Srv(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 Dhcp4 component.
+ writeFile(TEST_FILE, config_v4);
+ EXPECT_THROW(srv->init(TEST_FILE), BadValue);
+
+ // Now try to load a config with Dhcp4 full of nonsense.
+ writeFile(TEST_FILE, config_nonsense);
+ EXPECT_THROW(srv->init(TEST_FILE), BadValue);
+}
+
+/// This unit-test reads all files enumerated in configs-test.txt file, loads
+/// each of them and verify that they can be loaded.
+///
+/// @todo: Unfortunately, we have this test disabled, because all loaded
+/// configs use memfile, which attempts to create lease file in
+/// /usr/local/var/lib/kea/kea-leases4.csv. We have couple options here:
+/// a) disable persistence in example configs - a very bad thing to do
+/// as users will forget to reenable it and then will be surprised when their
+/// leases disappear
+/// b) change configs to store lease file in /tmp. It's almost as bad as the
+/// previous one. Users will then be displeased when all their leases are
+/// wiped. (most systems wipe /tmp during boot)
+/// c) read each config and rewrite it on the fly, so persistence is disabled.
+/// This is probably the way to go, but this is a work for a dedicated ticket.
+///
+/// Hence I'm leaving the test in, but it is disabled.
+TEST_F(JSONFileBackendTest, DISABLED_loadAllConfigs) {
+
+ // Create server first
+ boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv4Srv(0))
+ );
+
+ const char* configs_list = "configs-list.txt";
+ fstream configs(configs_list, ios::in);
+ ASSERT_TRUE(configs.is_open());
+ std::string config_name;
+ while (std::getline(configs, config_name)) {
+
+ // Ignore empty and commented lines
+ if (config_name.empty() || config_name[0] == '#') {
+ continue;
+ }
+
+ // Unit-tests usually do not print out anything, but in this case I
+ // think printing out tests configs is warranted.
+ std::cout << "Loading config file " << config_name << std::endl;
+
+ try {
+ srv->init(config_name);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "Exception thrown" << ex.what() << endl;
+ }
+ }
+}
+
+// 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 =
+ "{ \"Dhcp4\": {"
+ "\"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, \n"
+ "\"subnet4\": [ ],"
+ "\"valid-lifetime\": 4000 }"
+ "}";
+ writeFile(TEST_FILE, config);
+
+ // Create an instance of the server and initialize it.
+ boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new ControlledDhcpv4Srv(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.
+ HWAddrPtr hwaddr_expired(new HWAddr(HWAddr::fromText("00:01:02:03:04:05")));
+ Lease4Ptr lease_expired(new Lease4(IOAddress("10.0.0.1"), hwaddr_expired,
+ ClientIdPtr(), 60,
+ time(NULL) - 100, SubnetID(1)));
+
+ // 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.
+ HWAddrPtr hwaddr_reclaimed(new HWAddr(HWAddr::fromText("01:02:03:04:05:06")));
+ Lease4Ptr lease_reclaimed(new Lease4(IOAddress("10.0.0.2"), hwaddr_reclaimed,
+ ClientIdPtr(), 60,
+ time(NULL) - 1000, SubnetID(1)));
+ lease_reclaimed->state_ = Lease4::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.getLease4(IOAddress("10.0.0.1")));
+ ASSERT_TRUE(lease_mgr.getLease4(IOAddress("10.0.0.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.getLease4(IOAddress("10.0.0.1")));
+ ASSERT_TRUE(lease_expired);
+ EXPECT_TRUE(lease_expired->stateExpiredReclaimed());
+
+ // Second lease should have been removed.
+ ASSERT_NO_THROW(
+ lease_reclaimed = lease_mgr.getLease4(IOAddress("10.0.0.2"));
+ );
+ EXPECT_FALSE(lease_reclaimed);
+}
+
+// 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 =
+ "{ \"Dhcp4\": {"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, \n"
+ "\"subnet4\": [ ],"
+ "\"valid-lifetime\": 4000 }"
+ "}";
+ writeFile(TEST_FILE, config);
+
+ // Create an instance of the server and initialize it.
+ boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new ControlledDhcpv4Srv(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 CBControlDHCPv4::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 <<
+ "{ \"Dhcp4\": {"
+ "\"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"
+ "\"subnet4\": [ ],"
+ "\"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<NakedControlledDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedControlledDhcpv4Srv()));
+ 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/dhcp4/tests/marker_file.cc b/src/bin/dhcp4/tests/marker_file.cc
new file mode 100644
index 0000000..066e1f7
--- /dev/null
+++ b/src/bin/dhcp4/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/dhcp4/tests/marker_file.h.in b/src/bin/dhcp4/tests/marker_file.h.in
new file mode 100644
index 0000000..06f60d8
--- /dev/null
+++ b/src/bin/dhcp4/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/dhcp4/tests/out_of_range_unittest.cc b/src/bin/dhcp4/tests/out_of_range_unittest.cc
new file mode 100644
index 0000000..a11cfe6
--- /dev/null
+++ b/src/bin/dhcp4/tests/out_of_range_unittest.cc
@@ -0,0 +1,534 @@
+// Copyright (C) 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/.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp_ddns/ncr_msg.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp4/tests/dhcp4_client.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Set of JSON configurations used throughout the out of range
+/// tests.
+///
+/// - Configuration 0 - Reference configuration, all tests start with the
+/// server configured with this configuration:
+/// - 1 subnet: 10.0.0.0/24
+/// - with one pool 10.0.0.10 - 10.0.0.100
+/// - 1 address reservation (fixed host) for HW address:
+/// ff:ff:ff:ff:ff:01
+/// - 1 hostname reservation (dynamic host) for HW address:
+/// dd:dd:dd:dd:dd:01,
+/// - DDNS enabled
+/// - Configuration 1 - same subnet, different pool
+/// - Configuration 2 - same subnet, different pool, no reservations
+/// - Configuration 3 - different subnet with reservations
+/// - Configuration 4 - different subnet with no reservations
+/// - Configuration 5 - same as reference, no reservations
+///
+const char* OOR_CONFIGS[] = {
+// Configuration 0 - reference configuration
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"reservations\": [ "
+ " {"
+ " \"hw-address\": \"ff:ff:ff:ff:ff:01\","
+ " \"ip-address\": \"10.0.0.7\""
+ " },"
+ " {"
+ " \"hw-address\": \"dd:dd:dd:dd:dd:01\","
+ " \"hostname\": \"test.example.com\""
+ " }"
+ " ]"
+ "} ],"
+ "\"dhcp-ddns\": {"
+ " \"enable-updates\": true,"
+ " \"qualifying-suffix\": \"\""
+ "}"
+ "}",
+
+// Configuration 1 - same subnet as reference, different pool
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"pools\": [ { \"pool\": \"10.0.0.101-10.0.0.200\" } ],"
+ " \"reservations\": [ "
+ " {"
+ " \"hw-address\": \"ff:ff:ff:ff:ff:01\","
+ " \"ip-address\": \"10.0.0.7\""
+ " },"
+ " {"
+ " \"hw-address\": \"dd:dd:dd:dd:dd:01\","
+ " \"hostname\": \"reserved.example.com\""
+ " }"
+ " ]"
+ "} ],"
+ "\"dhcp-ddns\": {"
+ " \"enable-updates\": true,"
+ " \"qualifying-suffix\": \"\""
+ "}"
+ "}",
+
+// Configuration 2 - same subnet as reference, different pool, no reservations
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"pools\": [ { \"pool\": \"10.0.0.101-10.0.0.200\" } ],"
+ "} ],"
+ "\"dhcp-ddns\": {"
+ " \"enable-updates\": true,"
+ " \"qualifying-suffix\": \"\""
+ "}"
+ "}",
+
+
+// Configuration 3 - different subnet with reservations
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"pools\": [ { \"pool\": \"192.0.2.101-192.0.2.200\" } ],"
+ " \"reservations\": [ "
+ " {"
+ " \"hw-address\": \"ff:ff:ff:ff:ff:01\","
+ " \"ip-address\": \"192.0.2.7\""
+ " },"
+ " {"
+ " \"hw-address\": \"dd:dd:dd:dd:dd:01\","
+ " \"hostname\": \"reserved.example.com\""
+ " }"
+ " ]"
+ "} ],"
+ "\"dhcp-ddns\": {"
+ " \"enable-updates\": true,"
+ " \"qualifying-suffix\": \"\""
+ "}"
+ "}",
+
+// Configuration 4 - different subnet with no reservations
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"pools\": [ { \"pool\": \"192.0.2.101-192.0.2.200\" } ]"
+ "} ],"
+ "\"dhcp-ddns\": {"
+ " \"enable-updates\": true,"
+ " \"qualifying-suffix\": \"\""
+ "}"
+ "}",
+
+// Configuration 5 - same as reference, no reservations
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ]"
+ "} ],"
+ "\"dhcp-ddns\": {"
+ " \"enable-updates\": true,"
+ " \"qualifying-suffix\": \"\""
+ "}"
+ "}",
+
+};
+
+/// @brief Enum for indexing into the array of configurations.
+/// These were created to make the test cases easier to follow.
+enum CfgIndex {
+ REF_CFG = 0,
+ DIFF_POOL,
+ DIFF_POOL_NO_HR,
+ DIFF_SUBNET,
+ DIFF_SUBNET_NO_HR,
+ NO_HR
+};
+
+/// @brief Enum for specifying expected response to client renewal attempt
+enum RenewOutcome {
+ DOES_RENEW,
+ DOES_NOT_RENEW,
+ DOES_NOT_NAK
+};
+
+/// @brief Enum for specifying expected response to client release attempt
+enum ReleaseOutcome {
+ DOES_RELEASE,
+ DOES_NOT_RELEASE
+};
+
+/// @brief Test fixture class for testing various exchanges when the client's
+/// leased address is out of range due to configuration changes.
+class OutOfRangeTest : public Dhcpv4SrvTest {
+public:
+ D2ClientMgr& d2_mgr_;
+
+ /// @brief Constructor.
+ ///
+ /// Sets up fake interfaces.
+ OutOfRangeTest()
+ : Dhcpv4SrvTest(),
+ d2_mgr_(CfgMgr::instance().getD2ClientMgr()),
+ iface_mgr_test_config_(true) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Cleans up statistics after the test.
+ ~OutOfRangeTest() {
+ }
+
+ void configure(const std::string& config, Dhcp4Client& client) {
+ NakedDhcpv4Srv& server = *client.getServer();
+ ASSERT_NO_FATAL_FAILURE(Dhcpv4SrvTest::configure(config, server));
+ if (d2_mgr_.ddnsEnabled()) {
+ ASSERT_NO_THROW(server.startD2());
+ }
+ }
+
+ /// @briefVerify that a NameChangeRequest has been queued
+ ///
+ /// Checks that a NCR of a given type (ADD or REMOVE) for a given
+ /// ip address has been queued. If the NCR exits, it is processed
+ /// off the queue. Note the function expects there to be 1 and only
+ /// 1 NCR queued.
+ ///
+ /// @param type - NCR type expected, either CHG_ADD or CHG_REMOVE
+ /// @param addr - string containing the ip address expected in the NCR
+ void verifyNameChangeRequest(const isc::dhcp_ddns::NameChangeType type,
+ const std::string& addr) {
+ ASSERT_TRUE(d2_mgr_.getQueueSize() > 0);
+
+ isc::dhcp_ddns::NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = d2_mgr_.peekAt(0));
+ ASSERT_TRUE(ncr);
+
+ EXPECT_EQ(type, ncr->getChangeType());
+ EXPECT_EQ(addr, ncr->getIpAddress());
+
+ // Process the message off the queue
+ ASSERT_NO_THROW(d2_mgr_.runReadyIO());
+ }
+
+ /// @brief Conducts a single out-of-range test scenario
+ ///
+ /// Each test cycles consists of the following two stages, the first is
+ /// a set-up stage during which the server is configured with an initial,
+ /// reference, configuration and a client then verifies that it can acquire
+ /// and renew a lease. The second stage verifies that the server, having
+ /// been reconfigured such that the original lease is now "out-of-range",
+ /// responds correctly to the same client first attempting to renew the
+ /// lease and then attempting to release the lease.
+ ///
+ /// The test also expects the configuration to enable DDNS, and verifies
+ /// that a DNS add is done during stage one, and a DNS remove is done
+ /// in stage two as part of the release request processing.
+ ///
+ /// @param cfg_idx - index of the "replacement" configuration used to
+ /// reconfigure the server during stage two
+ /// @param hwaddress - text value, if not empty, to use as the hardware
+ /// address. This is used for host reservation tests.
+ /// in client queries
+ /// @param expected_address - text value of the expected address. If not
+ /// empty the test will fail if the server responds with a different lease
+ /// address
+ /// @param renew_outcome - expected server reaction in response to the
+ /// client's stage two renewal attempt.
+ /// @param release_outcome - expected server reaction in response to the
+ /// client's stage two release attempt. Currently defaults to DOES_RELEASE
+ /// as no cases have been identified which do otherwise.
+ void oorRenewReleaseTest(enum CfgIndex cfg_idx,
+ const std::string& hwaddress,
+ const std::string& expected_address,
+ enum RenewOutcome renew_outcome,
+ enum ReleaseOutcome release_outcome = DOES_RELEASE);
+
+ /// @brief Interface Manager's fake configuration control.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+
+};
+
+void
+OutOfRangeTest::oorRenewReleaseTest(enum CfgIndex cfg_idx,
+ const std::string& hwaddress,
+ const std::string& expected_address,
+ enum RenewOutcome renew_outcome,
+ enum ReleaseOutcome release_outcome) {
+ // STAGE ONE:
+
+ // Step 1 is to acquire the lease
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Configure DHCP server.
+ configure(OOR_CONFIGS[REF_CFG], client);
+
+ // Set the host name so DNS updates will be performed
+ client.includeHostname("test.example.com");
+
+ // Set the hw address if given one
+ if (!hwaddress.empty()) {
+ client.setHWAddress(hwaddress);
+ }
+
+ // Acquire the lease via DORA
+ ASSERT_NO_THROW(client.doDORA());
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Verify a lease was created
+ IOAddress leased_address = client.config_.lease_.addr_;
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(leased_address);
+ ASSERT_TRUE(lease);
+
+ // Check the expected address if given
+ if (!expected_address.empty()) {
+ ASSERT_EQ(leased_address, expected_address);
+ }
+
+ // Verify that a DNS add was requested
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, leased_address.toText());
+
+ // The address is still valid, let's verify the lease can be renewed
+ // Set the unicast destination address to indicate that it is a renewal.
+ client.setState(Dhcp4Client::RENEWING);
+ client.setDestAddress(IOAddress("10.0.0.1"));
+ ASSERT_NO_THROW(client.doRequest());
+
+ // Verify that we received an ACK to our renewal
+ resp = client.getContext().response_;
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ EXPECT_EQ(0, d2_mgr_.getQueueSize());
+
+ // STAGE TWO:
+
+ // Now reconfigure which should render our leased address out-of-range
+ configure(OOR_CONFIGS[cfg_idx], client);
+
+ // Try to renew after the configuration change..
+ ASSERT_NO_THROW(client.doRequest());
+ resp = client.getContext().response_;
+
+ switch(renew_outcome) {
+ case DOES_RENEW:
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ break;
+ case DOES_NOT_RENEW:
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPNAK, static_cast<int>(resp->getType()));
+ break;
+ case DOES_NOT_NAK:
+ ASSERT_FALSE(resp);
+ break;
+ }
+
+ // Verify that the lease still exists in the database as it has not
+ // been explicitly released.
+ lease = LeaseMgrFactory::instance().getLease4(leased_address);
+ ASSERT_TRUE(lease);
+
+ // Recreate the client lease info, a preceding NAK will have wiped it out
+ client.createLease(leased_address, lease->valid_lft_);
+
+ // Send the release. The server should remove it from the DB and
+ // request DNS remove.
+ ASSERT_NO_THROW(client.doRelease());
+
+ lease = LeaseMgrFactory::instance().getLease4(leased_address);
+
+ if (release_outcome == DOES_RELEASE) {
+ EXPECT_FALSE(lease);
+ // Verify the DNS remove was queued.
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE,
+ leased_address.toText());
+ } else {
+ // Lease should still exist, and no NCR should be queued.
+ EXPECT_TRUE(lease);
+ EXPECT_EQ(0, d2_mgr_.getQueueSize());
+ }
+}
+
+
+// Verifies that once-valid lease, whose address is no longer
+// within the subnet's pool:
+//
+// a: Is NAKed upon a renewal attempt
+// b: Is released properly upon release, including DNS removal
+//
+TEST_F(OutOfRangeTest, dynamicOutOfPool) {
+
+ std::string hwaddress = "";
+ std::string expected_address = "";
+
+ oorRenewReleaseTest(DIFF_POOL, hwaddress, expected_address,
+ DOES_NOT_NAK);
+
+}
+
+// Verifies that once-valid lease whose address is no longer
+// within any configured subnet:
+//
+// a: Is NAKed upon a renewal attempt
+// b: Is released properly upon release, including DNS removal
+//
+TEST_F(OutOfRangeTest, dynamicOutOfSubnet) {
+
+ std::string hwaddress = "";
+ std::string expected_address = "";
+
+ oorRenewReleaseTest(DIFF_SUBNET, hwaddress, expected_address,
+ DOES_NOT_RENEW);
+}
+
+// Test verifies that once-valid dynamic address host reservation,
+// whose address is no longer within the subnet's pool:
+//
+// a: Is NAKed upon a renewal attempt
+// b: Is released properly upon release, including DNS removal
+//
+TEST_F(OutOfRangeTest, dynamicHostOutOfPool) {
+ std::string hwaddress = "dd:dd:dd:dd:dd:01";
+ std::string expected_address = "";
+
+ oorRenewReleaseTest(DIFF_POOL, hwaddress, expected_address, DOES_NOT_NAK);
+}
+
+// Test verifies that once-valid dynamic address host reservation,
+// whose address is no longer within any configured subnet:
+//
+// a: Is NAKed upon a renewal attempt
+// b: Is released properly upon release, including DNS removal
+//
+TEST_F(OutOfRangeTest, dynamicHostOutOfSubnet) {
+ std::string hwaddress = "dd:dd:dd:dd:dd:01";
+ std::string expected_address = "";
+
+ oorRenewReleaseTest(DIFF_SUBNET, hwaddress, expected_address,
+ DOES_NOT_RENEW);
+}
+
+// Test verifies that once-valid dynamic address host reservation,
+// whose address is within the configured subnet and pool, but whose
+// reservation has been removed:
+//
+// a: Is allowed to renew
+// b: Is released properly upon release, including DNS removal
+//
+TEST_F(OutOfRangeTest, dynamicHostReservationRemoved) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ std::string hwaddress = "dd:dd:dd:dd:dd:01";
+ std::string expected_address = "";
+
+ oorRenewReleaseTest(NO_HR, hwaddress, expected_address, DOES_RENEW);
+}
+
+// Test verifies that once-valid dynamic address host reservation,
+// whose address is no longer within any configured subnet, and which
+// no longer has reservation defined:
+//
+// a: Is NAKed upon a renewal attempt
+// b: Is released properly upon release, including DNS removal
+//
+TEST_F(OutOfRangeTest, dynamicHostOutOfSubnetReservationRemoved) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ std::string hwaddress = "dd:dd:dd:dd:dd:01";
+ std::string expected_address = "";
+
+ oorRenewReleaseTest(DIFF_SUBNET_NO_HR, hwaddress, expected_address,
+ DOES_NOT_RENEW);
+}
+
+// Test verifies that once-valid in-subnet fixed-address host reservation,
+// after the subnet pool changes:
+//
+// a: Is NAK'd upon a renewal attempt
+// b: Is released properly upon release, including DNS removal
+//
+TEST_F(OutOfRangeTest, fixedHostOutOfSubnet) {
+ std::string hwaddress = "ff:ff:ff:ff:ff:01";
+ std::string expected_address = "10.0.0.7";
+
+ oorRenewReleaseTest(DIFF_SUBNET, hwaddress, expected_address,
+ DOES_NOT_RENEW);
+}
+
+
+// Test verifies that once-valid in-subnet fixed-address host reservation,
+// after the subnet pool is changed:
+//
+// a: Is ACK'd upon a renewal attempt
+// b: Is released properly upon release, including DNS removal
+//
+TEST_F(OutOfRangeTest, fixedHostDifferentPool) {
+ std::string hwaddress = "ff:ff:ff:ff:ff:01";
+ std::string expected_address = "10.0.0.7";
+
+ oorRenewReleaseTest(DIFF_POOL, hwaddress, expected_address, DOES_RENEW);
+}
+
+// Test verifies that once-valid in-subnet fixed-address host reservation,
+// whose reservation has been removed from the configuration
+//
+// a: Is NAK'd upon a renewal attempt
+// b: Is released properly upon release, including DNS removal
+//
+TEST_F(OutOfRangeTest, fixedHostReservationRemoved) {
+ std::string hwaddress = "ff:ff:ff:ff:ff:01";
+ std::string expected_address = "10.0.0.7";
+
+ oorRenewReleaseTest(NO_HR, hwaddress, expected_address, DOES_NOT_NAK);
+}
+
+// Test verifies that once-valid fixed address host reservation,
+// whose address is no longer within any configured subnet
+//
+// a: Is NAKed upon a renewal attempt
+// b: Is released properly upon release, including DNS removal
+//
+TEST_F(OutOfRangeTest, fixedHostOutOfSubnetReservationRemoved) {
+ std::string hwaddress = "ff:ff:ff:ff:ff:01";
+ std::string expected_address = "10.0.0.7";
+
+ oorRenewReleaseTest(DIFF_SUBNET_NO_HR, hwaddress, expected_address,
+ DOES_NOT_RENEW);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/parser_unittest.cc b/src/bin/dhcp4/tests/parser_unittest.cc
new file mode 100644
index 0000000..9a7e0db
--- /dev/null
+++ b/src/bin/dhcp4/tests/parser_unittest.cc
@@ -0,0 +1,973 @@
+// Copyright (C) 2016-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 <dhcp4/parser_context.h>
+#include <dhcpsrv/parsers/simple_parser4.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/io_utils.h>
+#include <testutils/log_utils.h>
+#include <testutils/user_context_utils.h>
+
+#include <gtest/gtest.h>
+
+#include <fstream>
+#include <set>
+
+#include <boost/algorithm/string.hpp>
+
+using namespace isc::data;
+using namespace isc::test;
+using namespace std;
+
+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());
+}
+
+/// @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, Parser4Context::ParserType parser_type,
+ bool compare = true) {
+ SCOPED_TRACE("\n=== tested config ===\n" + txt + "=====================");
+
+ ConstElementPtr test_json;
+ ASSERT_NO_THROW({
+ try {
+ Parser4Context 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, Parser4Context::PARSER_JSON);
+}
+
+TEST(ParserTest, listInList) {
+ string txt = "[ [ \"Britain\", \"Wales\", \"Scotland\" ], "
+ "[ \"Pomorze\", \"Wielkopolska\", \"Tatry\"] ]";
+ testParser(txt, Parser4Context::PARSER_JSON);
+}
+
+TEST(ParserTest, nestedMaps) {
+ string txt = "{ \"europe\": { \"UK\": { \"London\": { \"street\": \"221B Baker\" }}}}";
+ testParser(txt, Parser4Context::PARSER_JSON);
+}
+
+TEST(ParserTest, nestedLists) {
+ string txt = "[ \"half\", [ \"quarter\", [ \"eighth\", [ \"sixteenth\" ]]]]";
+ testParser(txt, Parser4Context::PARSER_JSON);
+}
+
+TEST(ParserTest, listsInMaps) {
+ string txt = "{ \"constellations\": { \"orion\": [ \"rigel\", \"betelgeuse\" ], "
+ "\"cygnus\": [ \"deneb\", \"albireo\"] } }";
+ testParser(txt, Parser4Context::PARSER_JSON);
+}
+
+TEST(ParserTest, mapsInLists) {
+ string txt = "[ { \"body\": \"earth\", \"gravity\": 1.0 },"
+ " { \"body\": \"mars\", \"gravity\": 0.376 } ]";
+ testParser(txt, Parser4Context::PARSER_JSON);
+}
+
+TEST(ParserTest, types) {
+ string txt = "{ \"string\": \"foo\","
+ "\"integer\": 42,"
+ "\"boolean\": true,"
+ "\"map\": { \"foo\": \"bar\" },"
+ "\"list\": [ 1, 2, 3 ],"
+ "\"null\": null }";
+ testParser(txt, Parser4Context::PARSER_JSON);
+}
+
+TEST(ParserTest, keywordJSON) {
+ string txt = "{ \"name\": \"user\","
+ "\"type\": \"password\","
+ "\"user\": \"name\","
+ "\"password\": \"type\" }";
+ testParser(txt, Parser4Context::PARSER_JSON);
+}
+
+TEST(ParserTest, keywordDhcp4) {
+ string txt = "{ \"Dhcp4\": { \"interfaces-config\": {"
+ " \"interfaces\": [ \"type\", \"htype\" ] },\n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"interface\": \"test\" } ],\n"
+ "\"valid-lifetime\": 4000 } }";
+ testParser(txt, Parser4Context::PARSER_DHCP4);
+}
+
+// Tests if bash (#) comments are supported. That's the only comment type that
+// was supported by the old parser.
+TEST(ParserTest, bashComments) {
+ string txt= "{ \"Dhcp4\": { \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},\n"
+ "# this is a comment\n"
+ "\"rebind-timer\": 2000, \n"
+ "# lots of comments here\n"
+ "# and here\n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 } }";
+ testParser(txt, Parser4Context::PARSER_DHCP4, false);
+}
+
+// Tests if C++ (//) comments can start anywhere, not just in the first line.
+TEST(ParserTest, cppComments) {
+ string txt= "{ \"Dhcp4\": { \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},\n"
+ "\"rebind-timer\": 2000, // everything after // is ignored\n"
+ "\"renew-timer\": 1000, // this will be ignored, too\n"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 } }";
+ testParser(txt, Parser4Context::PARSER_DHCP4, false);
+}
+
+// Tests if bash (#) comments can start anywhere, not just in the first line.
+TEST(ParserTest, bashCommentsInline) {
+ string txt= "{ \"Dhcp4\": { \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},\n"
+ "\"rebind-timer\": 2000, # everything after # is ignored\n"
+ "\"renew-timer\": 1000, # this will be ignored, too\n"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 } }";
+ testParser(txt, Parser4Context::PARSER_DHCP4, false);
+}
+
+// Tests if multi-line C style comments are handled correctly.
+TEST(ParserTest, multilineComments) {
+ string txt= "{ \"Dhcp4\": { \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},\n"
+ " /* 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"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 } }";
+ testParser(txt, Parser4Context::PARSER_DHCP4, false);
+}
+
+// Tests if embedded comments are handled correctly.
+TEST(ParserTest, embbededComments) {
+ string txt= "{ \"Dhcp4\": { \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},\n"
+ "\"comment\": \"a comment\",\n"
+ "\"rebind-timer\": 2000,\n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet4\": [ { "
+ " \"user-context\": { \"comment\": \"indirect\" },"
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"user-context\": { \"compatible\": true },"
+ "\"valid-lifetime\": 4000 } }";
+ testParser(txt, Parser4Context::PARSER_DHCP4, 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 {
+ Parser4Context ctx;
+ test_json = ctx.parseFile(fname, Parser4Context::PARSER_DHCP4);
+ } 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 Parser4. Both JSON trees are then compared.
+TEST(ParserTest, file) {
+ vector<string> configs = { "advanced.json" ,
+ "all-keys.json",
+ "all-options.json",
+ "backends.json",
+ "classify.json",
+ "classify2.json",
+ "comments.json",
+ "dhcpv4-over-dhcpv6.json",
+ "global-reservations.json",
+ "hooks.json",
+ "leases-expiration.json",
+ "multiple-options.json",
+ "mysql-reservations.json",
+ "pgsql-reservations.json",
+ "reservations.json",
+ "several-subnets.json",
+ "shared-network.json",
+ "single-subnet.json",
+ "vendor-specific.json",
+ "vivso.json",
+ "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;
+ Parser4Context ctx;
+ string fname = string(CFG_EXAMPLES) + "/" + "all-keys.json";
+ EXPECT_NO_THROW(json = ctx.parseFile(fname, Parser4Context::PARSER_DHCP4));
+ EXPECT_NO_THROW(json = json->get("Dhcp4"));
+ SimpleParser4 parser;
+ EXPECT_NO_THROW(parser.checkKeywords(parser.GLOBAL4_PARAMETERS, json));
+}
+
+// Basic test that checks if it's possible to specify outbound-interface.
+TEST(ParserTest, outboundIface) {
+ std::string fname = string(CFG_EXAMPLES) + "/" + "advanced.json";
+ Parser4Context ctx;
+ ConstElementPtr test_json = ctx.parseFile(fname, Parser4Context::PARSER_DHCP4);
+
+ ConstElementPtr tmp;
+ tmp = test_json->get("Dhcp4");
+ ASSERT_TRUE(tmp);
+
+ tmp = tmp->get("interfaces-config");
+ ASSERT_TRUE(tmp);
+
+ tmp = tmp->get("outbound-interface");
+ ASSERT_TRUE(tmp);
+ EXPECT_EQ(Element::string, tmp->getType());
+ EXPECT_EQ("use-routing", tmp->stringValue());
+}
+
+/// @brief Tests error conditions in Dhcp4Parser
+///
+/// @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,
+ Parser4Context::ParserType parser_type,
+ const std::string& msg) {
+ SCOPED_TRACE("\n=== tested config ===\n" + txt + "=====================");
+
+ try {
+ Parser4Context ctx;
+ ConstElementPtr parsed = ctx.parseString(txt, parser_type);
+ FAIL() << "Expected Dhcp4ParseError but nothing was raised (expected: "
+ << msg << ")";
+ }
+ catch (const Dhcp4ParseError& ex) {
+ EXPECT_EQ(msg, ex.what());
+ }
+ catch (...) {
+ FAIL() << "Expected Dhcp4ParseError but something else was raised";
+ }
+}
+
+// Verify that error conditions are handled correctly.
+TEST(ParserTest, errors) {
+ // no input
+ testError("", Parser4Context::PARSER_JSON,
+ "<string>:1.1: syntax error, unexpected end of file");
+ testError(" ", Parser4Context::PARSER_JSON,
+ "<string>:1.2: syntax error, unexpected end of file");
+ testError("\n", Parser4Context::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file");
+ testError("\t", Parser4Context::PARSER_JSON,
+ "<string>:1.2: syntax error, unexpected end of file");
+ testError("\r", Parser4Context::PARSER_JSON,
+ "<string>:1.2: syntax error, unexpected end of file");
+
+ // comments
+ testError("# nothing\n",
+ Parser4Context::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file");
+ testError(" #\n",
+ Parser4Context::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file");
+ testError("// nothing\n",
+ Parser4Context::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file");
+ testError("/* nothing */\n",
+ Parser4Context::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file");
+ testError("/* no\nthing */\n",
+ Parser4Context::PARSER_JSON,
+ "<string>:3.1: syntax error, unexpected end of file");
+ testError("/* no\nthing */\n\n",
+ Parser4Context::PARSER_JSON,
+ "<string>:4.1: syntax error, unexpected end of file");
+ testError("/* nothing\n",
+ Parser4Context::PARSER_JSON,
+ "Comment not closed. (/* in line 1");
+ testError("\n\n\n/* nothing\n",
+ Parser4Context::PARSER_JSON,
+ "Comment not closed. (/* in line 4");
+ testError("{ /* */*/ }\n",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.3-8: Invalid character: *");
+ testError("{ /* // *// }\n",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.3-11: Invalid character: /");
+ testError("{ /* // */// }\n",
+ Parser4Context::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file, "
+ "expecting }");
+
+ // includes
+ testError("<?\n",
+ Parser4Context::PARSER_JSON,
+ "Directive not closed.");
+ testError("<?include\n",
+ Parser4Context::PARSER_JSON,
+ "Directive not closed.");
+ string file = string(CFG_EXAMPLES) + "/" + "single-subnet.json";
+ testError("<?include \"" + file + "\"\n",
+ Parser4Context::PARSER_JSON,
+ "Directive not closed.");
+ testError("<?include \"/foo/bar\" ?>/n",
+ Parser4Context::PARSER_JSON,
+ "Can't open include file /foo/bar");
+
+ // JSON keywords
+ testError("{ \"foo\": True }",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.10-13: JSON true reserved keyword is lower case only");
+ testError("{ \"foo\": False }",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.10-14: JSON false reserved keyword is lower case only");
+ testError("{ \"foo\": NULL }",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.10-13: JSON null reserved keyword is lower case only");
+ testError("{ \"foo\": Tru }",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.10: Invalid character: T");
+ testError("{ \"foo\": nul }",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.10: Invalid character: n");
+
+ // numbers
+ testError("123",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:1.1-3: syntax error, unexpected integer, "
+ "expecting {");
+ testError("-456",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:1.1-4: syntax error, unexpected integer, "
+ "expecting {");
+ testError("-0001",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:1.1-5: syntax error, unexpected integer, "
+ "expecting {");
+ testError("1234567890123456789012345678901234567890",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.1-40: Failed to convert "
+ "1234567890123456789012345678901234567890"
+ " to an integer.");
+ testError("-3.14e+0",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:1.1-8: syntax error, unexpected floating point, "
+ "expecting {");
+ testError("1e50000",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.1-7: Failed to convert 1e50000 "
+ "to a floating point.");
+
+ // strings
+ testError("\"aabb\"",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:1.1-6: syntax error, unexpected constant string, "
+ "expecting {");
+ testError("{ \"aabb\"err",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.9: Invalid character: e");
+ testError("{ err\"aabb\"",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.3: Invalid character: e");
+ testError("\"a\n\tb\"",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.1-6 (near 2): Invalid control in \"a\n\tb\"");
+ testError("\"a\n\\u12\"",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.1-8 (near 2): Invalid control in \"a\n\\u12\"");
+ testError("\"a\\n\\tb\"",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:1.1-8: syntax error, unexpected constant string, "
+ "expecting {");
+ testError("\"a\\x01b\"",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.1-8 (near 3): Bad escape in \"a\\x01b\"");
+ testError("\"a\\u0162\"",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.1-9 (near 4): Unsupported unicode escape "
+ "in \"a\\u0162\"");
+ testError("\"a\\u062z\"",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.1-9 (near 3): Bad escape in \"a\\u062z\"");
+ testError("\"abc\\\"",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.1-6 (near 6): Overflow escape in \"abc\\\"");
+ testError("\"a\\u006\"",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.1-8 (near 3): Overflow unicode escape "
+ "in \"a\\u006\"");
+ testError("\"\\u\"",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.1-4 (near 2): Overflow unicode escape in \"\\u\"");
+ testError("\"\\u\x02\"",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.1-5 (near 2): Bad escape in \"\\u\x02\"");
+ testError("\"\\u\\\"foo\"",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.1-5 (near 2): Bad escape in \"\\u\\\"...");
+ testError("\"\x02\\u\"",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.1-5 (near 1): Invalid control in \"\x02\\u\"");
+
+ // from data_unittest.c
+ testError("\\a",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.1: Invalid character: \\");
+ testError("\\",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.1: Invalid character: \\");
+ testError("\\\"\\\"",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.1: Invalid character: \\");
+
+ // want a map
+ testError("[]\n",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:1.1: syntax error, unexpected [, "
+ "expecting {");
+ testError("[]\n",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:1.1: syntax error, unexpected [, "
+ "expecting {");
+ testError("{ 123 }\n",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.3-5: syntax error, unexpected integer, "
+ "expecting }");
+ testError("{ 123 }\n",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:1.3-5: syntax error, unexpected integer, "
+ "expecting Dhcp4");
+ testError("{ \"foo\" }\n",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.9: syntax error, unexpected }, "
+ "expecting :");
+ testError("{ \"foo\" }\n",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:1.3-7: syntax error, unexpected constant string, "
+ "expecting Dhcp4");
+ testError("{ \"foo\":null }\n",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:1.3-7: syntax error, unexpected constant string, "
+ "expecting Dhcp4");
+ testError("{ \"Logging\":null }\n",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:1.3-11: syntax error, unexpected constant string, "
+ "expecting Dhcp4");
+ testError("{ \"Dhcp4\" }\n",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:1.11: syntax error, unexpected }, "
+ "expecting :");
+ testError("{}{}\n",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.3: syntax error, unexpected {, "
+ "expecting end of file");
+
+ // duplicate in map
+ testError("{ \"foo\": 1, \"foo\": true }\n",
+ Parser4Context::PARSER_JSON,
+ "<string>:1:13: duplicate foo entries in "
+ "JSON map (previous at <string>:1:10)");
+
+ // bad commas
+ testError("{ , }\n",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.3: syntax error, unexpected \",\", "
+ "expecting }");
+ testError("{ , \"foo\":true }\n",
+ Parser4Context::PARSER_JSON,
+ "<string>:1.3: syntax error, unexpected \",\", "
+ "expecting }");
+
+ // bad type
+ testError("{ \"Dhcp4\":{\n"
+ " \"valid-lifetime\":false }}\n",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:2.20-24: syntax error, unexpected boolean, "
+ "expecting integer");
+
+ // unknown keyword
+ testError("{ \"Dhcp4\":{\n"
+ " \"valid_lifetime\":600 }}\n",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:2.2-17: got unexpected keyword "
+ "\"valid_lifetime\" in Dhcp4 map.");
+
+ // missing parameter
+ testError("{ \"name\": \"foo\",\n"
+ " \"code\": 123 }\n",
+ Parser4Context::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("{ \"Dhcp4\":{\n"
+ " \"comment\": true,\n"
+ " \"valid-lifetime\": 600 }}\n",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:2.14-17: syntax error, unexpected boolean, "
+ "expecting constant string");
+
+ testError("{ \"Dhcp4\":{\n"
+ " \"user-context\": \"a comment\",\n"
+ " \"valid-lifetime\": 600 }}\n",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:2.19-29: syntax error, unexpected constant string, "
+ "expecting {");
+
+ testError("{ \"Dhcp4\":{\n"
+ " \"comment\": \"a comment\",\n"
+ " \"comment\": \"another one\",\n"
+ " \"valid-lifetime\": 600 }}\n",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:3.3-11: duplicate user-context/comment entries "
+ "(previous at <string>:2:3)");
+
+ testError("{ \"Dhcp4\":{\n"
+ " \"user-context\": { \"version\": 1 },\n"
+ " \"user-context\": { \"one\": \"only\" },\n"
+ " \"valid-lifetime\": 600 }}\n",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:3.3-16: duplicate user-context entries "
+ "(previous at <string>:2:19)");
+
+ testError("{ \"Dhcp4\":{\n"
+ " \"user-context\": { \"comment\": \"indirect\" },\n"
+ " \"comment\": \"a comment\",\n"
+ " \"valid-lifetime\": 600 }}\n",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:3.3-11: duplicate user-context/comment entries "
+ "(previous at <string>:2:19)");
+
+ // duplicate Dhcp4 entries
+ testError("{ \"Dhcp4\":{\n"
+ " \"comment\": \"first\" },\n"
+ " \"Dhcp4\":{\n"
+ " \"comment\": \"second\" }}\n",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:3.3-9: syntax error, unexpected Dhcp4, expecting \",\" or }");
+
+ // duplicate of not string entries
+ testError("{ \"Dhcp4\":{\n"
+ " \"subnet4\": [],\n"
+ " \"subnet4\": [] }}\n",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:3:2: duplicate subnet4 entries in "
+ "Dhcp4 map (previous at <string>:2:2)");
+
+ // duplicate of string entries
+ testError("{\n"
+ " \"server-hostname\": \"nohost\",\n"
+ " \"server-hostname\": \"nofile\" }\n",
+ Parser4Context::PARSER_HOST_RESERVATION,
+ "<string>:3:2: duplicate server-hostname entries in "
+ "reservations map (previous at <string>:2:21)");
+}
+
+// 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 {
+ Parser4Context ctx;
+ result = ctx.parseString(json, Parser4Context::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 are recognized properly.
+TEST(ParserTest, unicodeSlash) {
+ // check the 4 possible encodings of solidus '/'
+ ConstElementPtr result;
+ string json = "\"/\\/\\u002f\\u002F\"";
+ ASSERT_NO_THROW(
+ try {
+ Parser4Context ctx;
+ result = ctx.parseString(json, Parser4Context::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) {
+ Parser4Context ctx;
+ ElementPtr json;
+ EXPECT_NO_THROW(json = ctx.parseFile(fname, Parser4Context::PARSER_DHCP4));
+ 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 (dhcp4_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 + "all-keys.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);
+ Parser4Context ctx;
+ EXPECT_THROW(ctx.parseString(before + after,
+ Parser4Context::PARSER_DHCP4),
+ Dhcp4ParseError) << "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";
+ Parser4Context ctx;
+ ElementPtr sample_json;
+ EXPECT_NO_THROW(sample_json =
+ ctx.parseFile(sample_fname, Parser4Context::PARSER_DHCP4));
+ 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 = "DHCP4_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"({
+ "Dhcp4": {
+ "control-socket": {
+ "socket-name": "/tmp/kea-dhcp4-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-dhcp4.csv",
+ "persist": true,
+ "type": "memfile",
+ },
+ "loggers": [
+ {
+ "debuglevel": 99,
+ "name": "kea-dhcp4",
+ "output_options": [
+ {
+ "output": "stdout",
+ },
+ ],
+ "severity": "DEBUG",
+ },
+ ],
+ "multi-threading": {
+ "enable-multi-threading": false,
+ "packet-queue-size": 0,
+ "thread-pool-size": 0
+ },
+ "subnet4": [
+ {
+ "pools": [
+ {
+ "pool": "192.168.0.0/24",
+ },
+ ],
+ "subnet": "192.168.0.0/24",
+ },
+ ],
+ },
+})");
+ testParser(txt, Parser4Context::PARSER_DHCP4, 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.37");
+ addLog("<string>:44.12");
+ addLog("<string>:46.35");
+ addLog("<string>:47.8");
+ addLog("<string>:48.6");
+ addLog("<string>:49.4");
+ EXPECT_TRUE(checkFile());
+
+ // Test with many consecutive commas.
+ boost::replace_all(txt, ",", ",,,,");
+ testParser(txt, Parser4Context::PARSER_DHCP4, false);
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/bin/dhcp4/tests/release_unittest.cc b/src/bin/dhcp4/tests/release_unittest.cc
new file mode 100644
index 0000000..f2f66f3
--- /dev/null
+++ b/src/bin/dhcp4/tests/release_unittest.cc
@@ -0,0 +1,299 @@
+// 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 <cc/data.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp4/tests/dhcp4_client.h>
+#include <stats/stats_mgr.h>
+#include <boost/shared_ptr.hpp>
+#include <sstream>
+
+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 Release tests.
+///
+/// - Configuration 0:
+/// - Used for testing Release message processing
+/// - 1 subnet: 10.0.0.0/24
+/// - 1 pool: 10.0.0.10-10.0.0.100
+/// - Router option present: 10.0.0.200 and 10.0.0.201
+const char* RELEASE_CONFIGS[] = {
+// Configuration 0
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"routers\","
+ " \"data\": \"10.0.0.200,10.0.0.201\""
+ " } ]"
+ " } ]"
+ "}"
+};
+
+/// @brief Test fixture class for testing 4-way (DORA) exchanges.
+///
+/// @todo Currently there is a limit number of test cases covered here.
+/// In the future it is planned that the tests from the
+/// dhcp4_srv_unittest.cc will be migrated here and will use the
+/// @c Dhcp4Client class.
+class ReleaseTest : public Dhcpv4SrvTest {
+public:
+
+ enum ExpectedResult {
+ SHOULD_PASS,
+ SHOULD_FAIL
+ };
+
+ /// @brief Constructor.
+ ///
+ /// Sets up fake interfaces.
+ ReleaseTest()
+ : Dhcpv4SrvTest(),
+ iface_mgr_test_config_(true) {
+ }
+
+ /// @brief Performs 4-way exchange to obtain new lease.
+ ///
+ /// @param client Client to be used to obtain a lease.
+ void acquireLease(Dhcp4Client& client);
+
+ /// @brief Tests if the acquired lease is or is not released.
+ ///
+ /// @param hw_address_1 HW Address to be used to acquire the lease.
+ /// @param client_id_1 Client id to be used to acquire the lease.
+ /// @param hw_address_2 HW Address to be used to release the lease.
+ /// @param client_id_2 Client id to be used to release the lease.
+ /// @param expected_result SHOULD_PASS if the lease is expected to
+ /// be successfully released, or SHOULD_FAIL if the lease is expected
+ /// to not be released.
+ void acquireAndRelease(const std::string& hw_address_1,
+ const std::string& client_id_1,
+ const std::string& hw_address_2,
+ const std::string& client_id_2,
+ ExpectedResult expected_result);
+
+ /// @brief Interface Manager's fake configuration control.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+
+};
+
+void
+ReleaseTest::acquireLease(Dhcp4Client& client) {
+ // Perform 4-way exchange with the server but to not request any
+ // specific address in the DHCPDISCOVER message.
+ ASSERT_NO_THROW(client.doDORA());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // Response must not be relayed.
+ EXPECT_FALSE(resp->isRelayed());
+ // Make sure that the server id is present.
+ EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+ // Make sure that the client has got the lease with the requested address.
+ ASSERT_NE(client.config_.lease_.addr_.toText(), "0.0.0.0");
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(client.config_.lease_.addr_);
+ ASSERT_TRUE(lease);
+}
+
+void
+ReleaseTest::acquireAndRelease(const std::string& hw_address_1,
+ const std::string& client_id_1,
+ const std::string& hw_address_2,
+ const std::string& client_id_2,
+ ExpectedResult expected_result) {
+ CfgMgr::instance().clear();
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Configure DHCP server.
+ configure(RELEASE_CONFIGS[0], *client.getServer());
+ // Explicitly set the client id.
+ client.includeClientId(client_id_1);
+ // Explicitly set the HW address.
+ client.setHWAddress(hw_address_1);
+ // Perform 4-way exchange to obtain a new lease.
+ acquireLease(client);
+
+ std::stringstream name;
+
+ // Let's get the subnet-id and generate statistics name out of it
+ const Subnet4Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+ ASSERT_EQ(1, subnets->size());
+ name << "subnet[" << (*subnets->begin())->getID() << "].assigned-addresses";
+
+ ObservationPtr assigned_cnt = StatsMgr::instance().getObservation(name.str());
+ ASSERT_TRUE(assigned_cnt);
+ uint64_t before = assigned_cnt->getInteger().first;
+
+ // Remember the acquired address.
+ IOAddress leased_address = client.config_.lease_.addr_;
+
+ // Explicitly set the client id for DHCPRELEASE.
+ client.includeClientId(client_id_2);
+ // Explicitly set the HW address for DHCPRELEASE.
+ client.setHWAddress(hw_address_2);
+
+ // Send the release and make sure that the lease is removed from the
+ // server.
+ ASSERT_NO_THROW(client.doRelease());
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(leased_address);
+
+ assigned_cnt = StatsMgr::instance().getObservation(name.str());
+ ASSERT_TRUE(assigned_cnt);
+ uint64_t after = assigned_cnt->getInteger().first;
+
+ // We check if the release process was successful by checking if the
+ // lease is in the database. It is expected that it is not present,
+ // i.e. has been deleted with the release.
+ if (expected_result == SHOULD_PASS) {
+ EXPECT_FALSE(lease);
+
+ // The removal succeeded, so the assigned-addresses statistic should
+ // be decreased by one
+ EXPECT_EQ(before, after + 1);
+ } else {
+ EXPECT_TRUE(lease);
+
+ // The removal failed, so the assigned-address should be the same
+ // as before
+ EXPECT_EQ(before, after);
+ }
+}
+
+// This test checks that the client can acquire and release the lease.
+TEST_F(ReleaseTest, releaseNoIdentifierChange) {
+ acquireAndRelease("01:02:03:04:05:06", "12:14",
+ "01:02:03:04:05:06", "12:14",
+ SHOULD_PASS);
+}
+
+// This test verifies the release correctness in the following case:
+// - Client acquires new lease using HW address only
+// - Client sends the DHCPRELEASE with valid HW address and without
+// client identifier.
+// - The server successfully releases the lease.
+TEST_F(ReleaseTest, releaseHWAddressOnly) {
+ acquireAndRelease("01:02:03:04:05:06", "",
+ "01:02:03:04:05:06", "",
+ SHOULD_PASS);
+}
+
+// This test verifies the release correctness in the following case:
+// - Client acquires new lease using the client identifier and HW address
+// - Client sends the DHCPRELEASE with valid HW address but with no
+// client identifier.
+// - The server successfully releases the lease.
+TEST_F(ReleaseTest, releaseNoClientId) {
+ acquireAndRelease("01:02:03:04:05:06", "12:14",
+ "01:02:03:04:05:06", "",
+ SHOULD_PASS);
+}
+
+// This test verifies the release correctness in the following case:
+// - Client acquires new lease using HW address
+// - Client sends the DHCPRELEASE with valid HW address and some
+// client identifier.
+// - The server identifies the lease using HW address and releases
+// this lease.
+TEST_F(ReleaseTest, releaseNoClientId2) {
+ acquireAndRelease("01:02:03:04:05:06", "",
+ "01:02:03:04:05:06", "12:14",
+ SHOULD_PASS);
+}
+
+// This test checks the server's behavior in the following case:
+// - Client acquires new lease using the client identifier and HW address
+// - Client sends the DHCPRELEASE with the valid HW address but with invalid
+// client identifier.
+// - The server should not remove the lease.
+TEST_F(ReleaseTest, releaseNonMatchingClientId) {
+ acquireAndRelease("01:02:03:04:05:06", "12:14",
+ "01:02:03:04:05:06", "12:15:16",
+ SHOULD_FAIL);
+}
+
+// This test checks the server's behavior in the following case:
+// - Client acquires new lease using client identifier and HW address
+// - Client sends the DHCPRELEASE with the same client identifier but
+// different HW address.
+// - The server uses client identifier to find the client's lease and
+// releases it.
+TEST_F(ReleaseTest, releaseNonMatchingHWAddress) {
+ acquireAndRelease("01:02:03:04:05:06", "12:14",
+ "06:06:06:06:06:06", "12:14",
+ SHOULD_PASS);
+}
+
+// This test checks the server's behavior in the following case:
+// - Client acquires new lease.
+// - Client sends DHCPRELEASE with the ciaddr set to a different
+// address than it has acquired from the server.
+// - Server determines that the client is trying to release a
+// wrong address and will refuse to release.
+TEST_F(ReleaseTest, releaseNonMatchingIPAddress) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Configure DHCP server.
+ configure(RELEASE_CONFIGS[0], *client.getServer());
+ // Perform 4-way exchange to obtain a new lease.
+ acquireLease(client);
+
+ // Remember the acquired address.
+ IOAddress leased_address = client.config_.lease_.addr_;
+
+ // Modify the client's address to force it to release a different address
+ // than it has obtained from the server.
+ client.config_.lease_.addr_ = IOAddress(leased_address.toUint32() + 1);
+
+ // Send DHCPRELEASE and make sure it was unsuccessful, i.e. the lease
+ // remains in the database.
+ ASSERT_NO_THROW(client.doRelease());
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(leased_address);
+ ASSERT_TRUE(lease);
+}
+
+// This test verifies that an incoming RELEASE for an address within
+// a subnet that has been removed can still be released.
+TEST_F(ReleaseTest, releaseNoSubnet) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Configure DHCP server.
+ configure(RELEASE_CONFIGS[0], *client.getServer());
+ // Perform 4-way exchange to obtain a new lease.
+ acquireLease(client);
+
+ // Remember the acquired address.
+ IOAddress leased_address = client.config_.lease_.addr_;
+
+ // Release is as it was relayed
+ client.useRelay(true);
+
+ // Send the release
+ ASSERT_NO_THROW(client.doRelease());
+
+ // Check that the lease was removed
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(leased_address);
+ EXPECT_FALSE(lease);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/shared_network_unittest.cc b/src/bin/dhcp4/tests/shared_network_unittest.cc
new file mode 100644
index 0000000..f9c137c
--- /dev/null
+++ b/src/bin/dhcp4/tests/shared_network_unittest.cc
@@ -0,0 +1,2876 @@
+// 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 <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp/option.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_string.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_subnets4.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcp4/json_config_parser.h>
+#include <dhcp4/tests/dhcp4_client.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <stats/stats_mgr.h>
+#include <boost/pointer_cast.hpp>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+
+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 Array of server configurations used throughout the tests.
+const char* NETWORKS_CONFIG[] = {
+// Configuration #0
+// - 1 shared network with 2 subnets (interface specified)
+// - 1 "plain" subnet (different interface specified)
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"comment\": \"example\","
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.63 - 192.0.2.63\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.16 - 10.0.0.16\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.64/26\","
+ " \"id\": 1000,"
+ " \"interface\": \"eth0\","
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.65 - 192.0.2.65\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #1
+// - 1 shared networks with 1 subnet, relay ip specified
+// - 1 "plain" subnet, relay ip specified
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"relay\": {"
+ " \"ip-address\": \"192.3.5.6\""
+ " },"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.63 - 192.0.2.63\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.64/26\","
+ " \"id\": 1000,"
+ " \"relay\": {"
+ " \"ip-address\": \"192.1.2.3\""
+ " },"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.65 - 192.0.2.65\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #2
+// - 2 classes defined
+// - 1 shared network with 2 subnets (first has class restriction)
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"a-devices\","
+ " \"test\": \"option[93].hex == 0x0001\""
+ " },"
+ " {"
+ " \"name\": \"b-devices\","
+ " \"test\": \"option[93].hex == 0x0002\""
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"relay\": {"
+ " \"ip-address\": \"192.3.5.6\""
+ " },"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.63 - 192.0.2.63\""
+ " }"
+ " ],"
+ " \"client-class\": \"a-devices\""
+ " },"
+ " {"
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.16 - 10.0.0.16\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #3
+// - 2 classes specified
+// - 1 shared network with 2 subnets (each with class restriction)
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"a-devices\","
+ " \"test\": \"option[93].hex == 0x0001\""
+ " },"
+ " {"
+ " \"name\": \"b-devices\","
+ " \"test\": \"option[93].hex == 0x0002\""
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"relay\": {"
+ " \"ip-address\": \"192.3.5.6\""
+ " },"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.63 - 192.0.2.63\""
+ " }"
+ " ],"
+ " \"client-class\": \"a-devices\""
+ " },"
+ " {"
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.16 - 10.0.0.16\""
+ " }"
+ " ],"
+ " \"client-class\": \"b-devices\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #4
+// - 1 shared network with 2 subnets, each has one host reservation
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"relay\": {"
+ " \"ip-address\": \"192.3.5.6\""
+ " },"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"192.0.2.28\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.1 - 10.0.0.254\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"11:22:33:44:55:66\","
+ " \"ip-address\": \"10.0.0.29\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #5
+// - 1 shared network, with 2 subnets. Each has host reservation
+// - similar to config #4, but with different hw-address reserved
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"relay\": {"
+ " \"ip-address\": \"192.3.5.6\""
+ " },"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"11:22:33:44:55:66\","
+ " \"ip-address\": \"192.0.2.28\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.1 - 10.0.0.254\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"10.0.0.29\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #6
+// - 1 class
+// - 1 shared network, with 2 subnets. First has class restriction and
+// host reservation
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"a-devices\","
+ " \"test\": \"option[93].hex == 0x0001\""
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"relay\": {"
+ " \"ip-address\": \"192.3.5.6\""
+ " },"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\""
+ " }"
+ " ],"
+ " \"client-class\": \"a-devices\","
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"192.0.2.28\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.16 - 10.0.0.16\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #7
+// - 1 global option
+// - 1 shared network with some options and 2 subnets (the first one has extra
+// options)
+// - 1 plain subnet (that has an option)
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"log-servers\","
+ " \"data\": \"1.2.3.4\""
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.1.2.3\""
+ " },"
+ " {"
+ " \"name\": \"cookie-servers\","
+ " \"data\": \"10.6.5.4\""
+ " }"
+ " ],"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"routers\","
+ " \"data\": \"192.0.2.5\""
+ " },"
+ " {"
+ " \"name\": \"cookie-servers\","
+ " \"data\": \"10.5.4.3\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.63 - 192.0.2.63\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.16 - 10.0.0.16\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.64/26\","
+ " \"id\": 1000,"
+ " \"interface\": \"eth0\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"cookie-servers\","
+ " \"data\": \"10.1.1.1\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.65 - 192.0.2.65\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #8
+// - two shared networks, each with two subnets (each with interface specified)
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"192.0.2.64/26\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.65 - 192.0.2.127\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"name\": \"dog\","
+ " \"interface\": \"eth0\","
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"10.0.0.0/26\","
+ " \"id\": 1000,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.1 - 10.0.0.63\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"10.0.0.64/26\","
+ " \"id\": 10000,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.65 - 10.0.0.127\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #9
+// - 2 shared networks, each with relay ip address and 2 subnets
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"relay\": { \"ip-address\": \"10.1.2.3\" },"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"192.0.2.64/26\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.65 - 192.0.2.127\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"name\": \"dog\","
+ " \"relay\": { \"ip-address\": \"192.1.2.3\" },"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"10.0.0.0/26\","
+ " \"id\": 1000,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.1 - 10.0.0.63\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"10.0.0.64/26\","
+ " \"id\": 10000,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.65 - 10.0.0.127\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+// Configuration #10.
+// - 1 client class
+// - 1 shared network with two subnets (second has a host reservation)
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"class-with-bootfile\","
+ " \"boot-file-name\": \"/dev/null\""
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"relay\": {"
+ " \"ip-address\": \"192.3.5.6\""
+ " },"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.1 - 10.0.0.254\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"11:22:33:44:55:66\","
+ " \"ip-address\": \"10.0.0.29\","
+ " \"hostname\": \"test.example.org\","
+ " \"next-server\": \"10.10.10.10\","
+ " \"client-classes\": [ \"class-with-bootfile\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #11.
+// - global value of match-client-id set to false
+// - 1 shared network (match-client-id set to true) with 2 subnets
+// - the first subnet has match-client-id set to false
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"match-client-id\": false,"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"match-client-id\": true,"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"match-client-id\": false"
+ " },"
+ " {"
+ " \"subnet\": \"192.0.2.64/26\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.65 - 192.0.2.127\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #12.
+// - global value of match-client-id set to false
+// - 1 shared network (match-client-id set to false) with 2 subnets
+// - the first subnet has match-client-id set to false
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"match-client-id\": false,"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"match-client-id\": false,"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"match-client-id\": false"
+ " },"
+ " {"
+ " \"subnet\": \"192.0.2.64/26\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.65 - 192.0.2.127\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #13.
+// - 2 classes
+// - 2 shared networks, each with 1 subnet and client class restriction
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"a-devices\","
+ " \"test\": \"option[93].hex == 0x0001\""
+ " },"
+ " {"
+ " \"name\": \"b-devices\","
+ " \"test\": \"option[93].hex == 0x0002\""
+ " }"
+ " ],"
+ " \"valid-lifetime\": 600,"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"client-class\": \"a-devices\","
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.63 - 192.0.2.63\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"name\": \"dog\","
+ " \"interface\": \"eth1\","
+ " \"client-class\": \"b-devices\","
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"10.0.0.0/26\","
+ " \"id\": 1000,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.63 - 10.0.0.63\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+// Configuration #14
+// - 1 shared networks with 2 subnets, relay ip specified,
+// each relay has its own relay ip specified
+// - 1 "plain" subnet, relay ip specified
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"relay\": {"
+ " \"ip-address\": \"192.3.5.6\""
+ " },"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"relay\": {"
+ " \"ip-address\": \"192.1.1.1\""
+ " },"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.63 - 192.0.2.63\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 100,"
+ " \"relay\": {"
+ " \"ip-address\": \"192.2.2.2\""
+ " },"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.16 - 10.0.0.16\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.64/26\","
+ " \"id\": 1000,"
+ " \"relay\": {"
+ " \"ip-address\": \"192.3.3.3\""
+ " },"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.65 - 192.0.2.65\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #15
+// - two shared networks, each comes with its own server identifier.
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dhcp-server-identifier\","
+ " \"data\": \"1.2.3.4\""
+ " }"
+ " ],"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"name\": \"dog\","
+ " \"interface\": \"eth0\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dhcp-server-identifier\","
+ " \"data\": \"2.3.4.5\""
+ " }"
+ " ],"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"10.0.0.0/26\","
+ " \"id\": 1000,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.1 - 10.0.0.63\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #16
+// - 1 shared network with 1 subnet and 2 pools (first pool has class restriction)
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"a-devices\","
+ " \"test\": \"option[93].hex == 0x0001\""
+ " },"
+ " {"
+ " \"name\": \"b-devices\","
+ " \"test\": \"option[93].hex == 0x0002\""
+ " }"
+ " ],"
+ " \"valid-lifetime\": 600,"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\","
+ " \"client-class\": \"a-devices\""
+ " },"
+ " {"
+ " \"pool\": \"192.0.2.100 - 192.0.2.100\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #17
+// - 1 shared network with 1 subnet and 2 pools (each with class restriction)
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"a-devices\","
+ " \"test\": \"option[93].hex == 0x0001\""
+ " },"
+ " {"
+ " \"name\": \"b-devices\","
+ " \"test\": \"option[93].hex == 0x0002\""
+ " }"
+ " ],"
+ " \"valid-lifetime\": 600,"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\","
+ " \"client-class\": \"a-devices\""
+ " },"
+ " {"
+ " \"pool\": \"192.0.2.100 - 192.0.2.100\","
+ " \"client-class\": \"b-devices\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #18
+// - plain subnet and 2 pools (first pool has class restriction)
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"a-devices\","
+ " \"test\": \"option[93].hex == 0x0001\""
+ " },"
+ " {"
+ " \"name\": \"b-devices\","
+ " \"test\": \"option[93].hex == 0x0002\""
+ " }"
+ " ],"
+ " \"valid-lifetime\": 600,"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"id\": 10,"
+ " \"interface\": \"eth1\","
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\","
+ " \"client-class\": \"a-devices\""
+ " },"
+ " {"
+ " \"pool\": \"192.0.2.100 - 192.0.2.100\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #19
+// - plain subnet and 2 pools (each with class restriction)
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"a-devices\","
+ " \"test\": \"option[93].hex == 0x0001\""
+ " },"
+ " {"
+ " \"name\": \"b-devices\","
+ " \"test\": \"option[93].hex == 0x0002\""
+ " }"
+ " ],"
+ " \"valid-lifetime\": 600,"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"id\": 10,"
+ " \"interface\": \"eth1\","
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\","
+ " \"client-class\": \"a-devices\""
+ " },"
+ " {"
+ " \"pool\": \"192.0.2.100 - 192.0.2.100\","
+ " \"client-class\": \"b-devices\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #20
+// - a shared network with two subnets
+// - first subnet has a dynamic address pool
+// - second subnet has no address pool but it has a host reservation
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"relay\": {"
+ " \"ip-address\": \"192.3.5.6\""
+ " },"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.1 - 10.0.0.1\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"reservations\": ["
+ " {"
+ " \"circuit-id\": \"'charter950'\","
+ " \"ip-address\": \"192.0.2.28\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}"
+};
+
+/// @Brief Test fixture class for DHCPv4 server using shared networks.
+class Dhcpv4SharedNetworkTest : public Dhcpv4SrvTest {
+public:
+
+ /// @brief Constructor.
+ Dhcpv4SharedNetworkTest()
+ : Dhcpv4SrvTest(),
+ iface_mgr_test_config_(true) {
+ StatsMgr::instance().removeAll();
+ }
+
+ /// @brief Specifies authoritative flag value
+ ///
+ /// Used to generate authoritative configs
+ typedef enum AuthoritativeFlag {
+ AUTH_DEFAULT, // explicit value not specified (use default)
+ AUTH_YES, // defined explicitly as yes
+ AUTH_NO // defined explicitly as no
+ } AuthoritativeFlag;
+
+ /// @brief Returns subnet having specified address in range.
+ ///
+ /// @param address Address for which subnet is being searched.
+ /// @return Pointer to the subnet having an address in range or null pointer
+ /// if no subnet found.
+ Subnet4Ptr getConfiguredSubnet(const IOAddress& address) {
+ CfgSubnets4Ptr cfg = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4();
+ const Subnet4Collection* subnets = cfg->getAll();
+ for (auto subnet_it = subnets->cbegin(); subnet_it != subnets->cend();
+ ++subnet_it) {
+ if ((*subnet_it)->inRange(address)) {
+ return (*subnet_it);
+ }
+ }
+ return (Subnet4Ptr());
+ }
+
+ /// @brief Perform DORA exchange and checks the result
+ ///
+ /// This convenience method conducts DORA exchange with client
+ /// packets using hint values specified by third parameter.
+ /// The response is expected to be either ACK (ack = true) or
+ /// NAK (ack = false). The received address is checked against
+ /// exp_addr
+ ///
+ /// @param client client to perform the DORA exchange
+ /// @param exp_addr expected address (in yiaddr field)
+ /// @param hint the address the client is supposed to sent
+ /// (empty string means send 0.0.0.0)
+ /// @param ack expected response (true = ACK, false = NAK)
+ void
+ doDORA(Dhcp4Client& client, std::string exp_addr, std::string hint = "",
+ bool ack = true) {
+
+ if (hint.empty()) {
+ ASSERT_NO_THROW(client.doDORA());
+ } else {
+ boost::shared_ptr<IOAddress> addr(new IOAddress(hint));
+ ASSERT_NO_THROW(client.doDORA(addr));
+ }
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ EXPECT_EQ((ack ? DHCPACK : DHCPNAK), resp->getType());
+ if (ack) {
+ EXPECT_EQ(exp_addr, resp->getYiaddr().toText());
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(IOAddress(resp->getYiaddr()));
+ ASSERT_TRUE(lease);
+ // Make sure that the subnet id in the lease database is not messed up.
+ Subnet4Ptr subnet = getConfiguredSubnet(resp->getYiaddr());
+ ASSERT_TRUE(subnet);
+ ASSERT_EQ(subnet->getID(), lease->subnet_id_);
+
+ } else {
+ EXPECT_EQ("0.0.0.0", resp->getYiaddr().toText());
+ }
+ }
+
+ /// @brief Perform Discover/Offer exchange and checks the result
+ ///
+ /// This convenience method conducts Discover/Offer exchange with client
+ /// packets using hint values specified by third parameter.
+ /// The response is expected to be either ACK (ack = true) or
+ /// NAK (ack = false). The received address is checked against
+ /// exp_addr
+ ///
+ /// @param client client to perform the DORA exchange
+ /// @param exp_addr expected address (in yiaddr field)
+ /// @param hint the address the client is supposed to sent
+ /// (empty string means send 0.0.0.0)
+ /// @param ack expected response (true = ACK, false = NAK)
+ void
+ doDiscover(Dhcp4Client& client, std::string exp_addr, std::string hint,
+ bool ack = true) {
+
+ if (hint.empty()) {
+ ASSERT_NO_THROW(client.doDiscover());
+ } else {
+ boost::shared_ptr<IOAddress> addr(new IOAddress(hint));
+ ASSERT_NO_THROW(client.doDiscover(addr));
+ }
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ EXPECT_EQ((ack ? DHCPOFFER : DHCPNAK), resp->getType());
+ if (ack) {
+ EXPECT_EQ(exp_addr, resp->getYiaddr().toText());
+ } else {
+ EXPECT_EQ("0.0.0.0", resp->getYiaddr().toText());
+ }
+ }
+
+ /// @brief Perform Request/Reply exchange and checks the result
+ ///
+ /// This convenience method conducts Request/Reply exchange with client
+ /// packets using hint values specified by a parameter. The response is
+ /// expected to be either ACK if exp_addr has a non-empty length,
+ /// or NAK when exp_addr context is empty.
+ ///
+ /// @param client client to perform the DORA exchange
+ /// @param exp_addr expected address (in yiaddr field)
+ void
+ doRequest(Dhcp4Client& client, std::string exp_addr) {
+ ASSERT_NO_THROW(client.doRequest());
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ if (exp_addr.empty()) {
+ EXPECT_EQ(DHCPNAK, resp->getType());
+ EXPECT_EQ("0.0.0.0", resp->getYiaddr().toText());
+ } else {
+ EXPECT_EQ(DHCPACK, resp->getType());
+ EXPECT_EQ(exp_addr, resp->getYiaddr().toText());
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(IOAddress(resp->getYiaddr()));
+ ASSERT_TRUE(lease);
+ // Make sure that the subnet id in the lease database is not messed up.
+ Subnet4Ptr subnet = getConfiguredSubnet(resp->getYiaddr());
+ ASSERT_TRUE(subnet);
+ ASSERT_EQ(subnet->getID(), lease->subnet_id_);
+ }
+ }
+
+ /// @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().startLeaseStatsQuery4();
+ LeaseStatsRow row;
+ while (query->getNextRow(row)) {
+ // Only check valid leases.
+ if (row.lease_state_ == Lease::STATE_DEFAULT) {
+ ASSERT_EQ(row.state_count_, getStatsAssignedAddresses(row.subnet_id_))
+ << "test failed for subnet id " << row.subnet_id_;
+ }
+ }
+ }
+
+ /// @brief Retrieves subnet[id].assigned-addresses statistics for a subnet.
+ ///
+ /// @param subnet_id Identifier of a subnet for which statistics should be
+ /// retrieved.
+ /// @return Number of assigned addresses for a subnet.
+ int64_t getStatsAssignedAddresses(const SubnetID& subnet_id) const {
+ // Retrieve statistics name, e.g. subnet[1234].assigned-addresses.
+ const std::string stats_name = StatsMgr::generateName("subnet", subnet_id, "assigned-addresses");
+ // Top element is a map with a subnet[id].assigned-addresses parameter.
+ ConstElementPtr top_element = StatsMgr::instance().get(stats_name);
+ if (top_element && (top_element->getType() == Element::map)) {
+ // It contains two lists (nested).
+ ConstElementPtr first_list = top_element->get(stats_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 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 MAC address to the one that has a reservation.
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.setIfaceName("eth1");
+ client.setIfaceIndex(ETH1_INDEX);
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+ // Request domain-name-servers.
+ client.requestOptions(DHO_DOMAIN_NAME_SERVERS);
+
+ // Create server configuration.
+ configure(config, *client.getServer());
+
+ // Perform a DORA.
+ doDORA(client, "192.0.2.28", "192.0.2.28");
+
+ // Check response.
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ EXPECT_EQ(DHCPACK, resp->getType());
+ EXPECT_EQ("192.0.2.28", resp->getYiaddr().toText());
+
+ // Check domain-name-servers option.
+ OptionPtr opt = resp->getOption(DHO_DOMAIN_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option4AddrLstPtr servers =
+ boost::dynamic_pointer_cast<Option4AddrLst>(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ(ns_address, addrs[0].toText());
+ }
+
+ /// @brief returns authoritative flag as JSON string
+ /// @param f value to be specified (default, true or false)
+ string auth(AuthoritativeFlag f) {
+ switch (f) {
+ case AUTH_DEFAULT:
+ return ("");
+ case AUTH_YES:
+ return (" \"authoritative\": true,");
+ break;
+ case AUTH_NO:
+ return (" \"authoritative\": false,");
+ }
+ return ("");
+ }
+
+ /// @brief generates Config file with specified authoritative flag values
+ ///
+ /// The config file has the same structure:
+ /// - 1 shared network with 2 subnets (global authoritative flag)
+ /// - first subnet: authoritative (subnet1 flag here)
+ /// - second subnet: authoritative (subnet2 flag here)
+ ///
+ /// @param global governs presence/value of global authoritative flag
+ /// @param subnet1 governs presence/value of authoritative flag in subnet1
+ /// @param subnet2 governs presence/value of authoritative flag in subnet2
+ string generateAuthConfig(AuthoritativeFlag global, AuthoritativeFlag subnet1,
+ AuthoritativeFlag subnet2) {
+ string cfg = "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"comment\": \"example\",";
+ cfg += auth(global);
+ cfg +=
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,";
+ cfg += auth(subnet1);
+
+ cfg +=
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.63 - 192.0.2.63\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 100,";
+ cfg += auth(subnet2);
+ cfg +=
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.16 - 10.0.0.16\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ return (cfg);
+ }
+
+ /// @brief Destructor.
+ virtual ~Dhcpv4SharedNetworkTest() {
+ StatsMgr::instance().removeAll();
+ }
+
+ /// @brief Interface Manager's fake configuration control.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+};
+
+// Check user-context parsing
+TEST_F(Dhcpv4SharedNetworkTest, parse) {
+ // Create client
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+
+ // Don't use configure from utils
+ Parser4Context ctx;
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(NETWORKS_CONFIG[0], true));
+ ConstElementPtr status;
+ disableIfacesReDetect(json);
+ EXPECT_NO_THROW(status = configureDhcp4Server(*client1.getServer(), json));
+ ASSERT_TRUE(status);
+ int rcode;
+ ConstElementPtr comment = config::parseAnswer(rcode, status);
+ ASSERT_EQ(0, rcode) << " comment: " << comment->stringValue();
+ ASSERT_NO_THROW( {
+ CfgDbAccessPtr cfg_db = CfgMgr::instance().getStagingCfg()->getCfgDbAccess();
+ cfg_db->setAppendedParameters("universe=4");
+ cfg_db->createManagers();
+ } );
+ CfgMgr::instance().commit();
+
+ CfgSharedNetworks4Ptr cfg = CfgMgr::instance().getCurrentCfg()->getCfgSharedNetworks4();
+ SharedNetwork4Ptr 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(Dhcpv4SharedNetworkTest, poolInSharedNetworkShortage) {
+ // Create client #1
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ client1.setIfaceName("eth1");
+ client1.setIfaceIndex(ETH1_INDEX);
+
+ // Configure the server with one shared network including two subnets and
+ // one subnet outside of the shared network.
+ configure(NETWORKS_CONFIG[0], *client1.getServer());
+
+ // Client #1 requests an address in first subnet within a shared network.
+ // We'll send a hint of 192.0.2.63 and expect to get it.
+ testAssigned([this, &client1]() {
+ doDORA(client1, "192.0.2.63", "192.0.2.63");
+ });
+
+ // Client #2 The second client will request a lease and should be assigned
+ // an address from the second subnet.
+ Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+ client2.setIfaceName("eth1");
+ client2.setIfaceIndex(ETH1_INDEX);
+ testAssigned([this, &client2]() {
+ doDORA(client2, "10.0.0.16");
+ });
+
+ // Client #3. It sends DHCPDISCOVER which should be dropped by the server because
+ // the server has no more addresses to assign.
+ Dhcp4Client client3(client1.getServer(), Dhcp4Client::SELECTING);
+ client3.setIfaceName("eth1");
+ client3.setIfaceIndex(ETH1_INDEX);
+ testAssigned([&client3]() {
+ ASSERT_NO_THROW(client3.doDiscover());
+ Pkt4Ptr resp3 = client3.getContext().response_;
+ ASSERT_FALSE(resp3);
+ });
+
+ // Client #3 should be assigned an address if subnet 3 is selected for this client.
+ client3.setIfaceName("eth0");
+ client3.setIfaceIndex(ETH0_INDEX);
+ testAssigned([this, &client3]() {
+ doDORA(client3, "192.0.2.65");
+ });
+
+ // Client #1 should be able to renew its address.
+ client1.setState(Dhcp4Client::RENEWING);
+ testAssigned([this, &client1]() {
+ doRequest(client1, "192.0.2.63");
+ });
+
+ // Client #2 should be able to renew its address.
+ client2.setState(Dhcp4Client::RENEWING);
+ testAssigned([this, &client2]() {
+ doRequest(client2, "10.0.0.16");
+ });
+}
+
+// Returning client sends 4-way exchange.
+TEST_F(Dhcpv4SharedNetworkTest, returningClientStartsOver) {
+ // Create client.
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.setIfaceName("eth1");
+ client.setIfaceIndex(ETH1_INDEX);
+ client.includeClientId("01:02:03:04");
+
+ // Configure the server with one shared network including two subnets and
+ // one subnet outside of the shared network.
+ configure(NETWORKS_CONFIG[0], *client.getServer());
+
+ // Client requests an address in first subnet within a shared network.
+ // We'll send a hint of 192.0.2.63 and expect to get it.
+ testAssigned([this, &client]() {
+ doDORA(client, "192.0.2.63", "192.0.2.63");
+ });
+
+
+ // The client reboots and performs 4-way exchange again without a hint.
+ // It should be assigned the same (existing) lease.
+ testAssigned([this, &client]() {
+ doDORA(client, "192.0.2.63");
+ });
+}
+
+// Shared network is selected based on giaddr value (relay specified
+// on shared network level)
+TEST_F(Dhcpv4SharedNetworkTest, sharedNetworkSelectedByRelay1) {
+ // Create client #1. This is a relayed client which is using relay
+ // address matching configured shared network.
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ client1.useRelay(true, IOAddress("192.3.5.6"), IOAddress("10.0.0.2"));
+
+ // Configure the server with one shared network and one subnet outside of the
+ // shared network.
+ configure(NETWORKS_CONFIG[1], *client1.getServer());
+
+ // Client #1 should be assigned an address from shared network.
+ testAssigned([this, &client1] {
+ doDORA(client1, "192.0.2.63", "192.0.2.63");
+ });
+
+ // Create client #2. This is a relayed client which is using relay
+ // address matching subnet outside of the shared network.
+ Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+ client2.useRelay(true, IOAddress("192.1.2.3"), IOAddress("10.0.0.3"));
+ testAssigned([this, &client2] {
+ doDORA(client2, "192.0.2.65", "192.0.2.63");
+ });
+}
+
+// Shared network is selected based on giaddr value (relay specified
+// on subnet in shared network level). Note the relay ip is specified
+// on the shared network level, but its value is overridden on subnet
+// level.
+TEST_F(Dhcpv4SharedNetworkTest, sharedNetworkSelectedByRelay2) {
+ // Create client #1. This is a relayed client which is using relay
+ // address matching configured subnet 1 in shared network.
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ client1.useRelay(true, IOAddress("192.1.1.1"), IOAddress("10.0.0.2"));
+
+ // Configure the server with one shared network and one subnet outside of the
+ // shared network.
+ configure(NETWORKS_CONFIG[14], *client1.getServer());
+
+ // Client #1 should be assigned an address from shared network.
+ testAssigned([this, &client1] {
+ doDORA(client1, "192.0.2.63");
+ });
+
+ // Create client #2. This is a relayed client which is using relay
+ // address that is used for subnet 2 in the shared network.
+ Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+ client2.useRelay(true, IOAddress("192.2.2.2"), IOAddress("10.0.0.3"));
+ testAssigned([this, &client2] {
+ doDORA(client2, "10.0.0.16");
+ });
+
+ // Create client #3. This is a relayed client which is using relay
+ // address matching subnet outside of the shared network.
+ Dhcp4Client client3(client1.getServer(), Dhcp4Client::SELECTING);
+ client3.useRelay(true, IOAddress("192.3.3.3"), IOAddress("10.0.0.4"));
+ testAssigned([this, &client3] {
+ doDORA(client3, "192.0.2.65");
+ });
+}
+
+// Providing a hint for any address belonging to a shared network.
+TEST_F(Dhcpv4SharedNetworkTest, hintWithinSharedNetwork) {
+ // Create client.
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.setIfaceName("eth1");
+ client.setIfaceIndex(ETH1_INDEX);
+
+ // Configure the server with one shared network including two subnets and
+ // one subnet outside of the shared network.
+ 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.
+ testAssigned([this, &client] {
+ doDiscover(client, "192.0.2.63", "192.0.2.63");
+ });
+
+ // Similarly, we should be offered an address from another subnet within
+ // the same shared network when we ask for it.
+ testAssigned([this, &client] {
+ doDiscover(client, "10.0.0.16", "10.0.0.16");
+ });
+
+ // 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.
+ testAssigned([&client] {
+ ASSERT_NO_THROW(client.doDiscover(boost::shared_ptr<IOAddress>(new IOAddress("10.0.0.23"))));
+ });
+
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // We expect one of the two addresses available in this shared network.
+ EXPECT_EQ(DHCPOFFER, resp->getType());
+ if ((resp->getYiaddr() != IOAddress("10.0.0.16")) &&
+ (resp->getYiaddr() != IOAddress("192.0.2.63"))) {
+ ADD_FAILURE() << "Unexpected address offered by the server " << resp->getYiaddr();
+ }
+}
+
+// Access to a subnet within shared network is restricted by client
+// classification.
+TEST_F(Dhcpv4SharedNetworkTest, subnetInSharedNetworkSelectedByClass) {
+ // Create client #1
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ client1.useRelay(true, IOAddress("192.3.5.6"));
+
+ // Configure the server with one shared network including two subnets in
+ // it. The access to one of the subnets is restricted by client classification.
+ 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.
+ testAssigned([this, &client1] {
+ doDORA(client1, "10.0.0.16", "192.0.2.63");
+ });
+
+ // 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 option93 which would cause the client to be classified as "a-devices".
+ OptionPtr option93(new OptionUint16(Option::V4, 93, 0x0001));
+ client1.addExtraOption(option93);
+
+ // This time, the allocation of the address provided as hint should be successful.
+ testAssigned([this, &client1] {
+ doDORA(client1, "192.0.2.63", "192.0.2.63");
+ });
+
+ // Client 2 should be assigned an address from the unrestricted subnet.
+ Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+ client2.useRelay(true, IOAddress("192.3.5.6"));
+ client2.setIfaceName("eth1");
+ client2.setIfaceIndex(ETH1_INDEX);
+ testAssigned([this, &client2] {
+ doDORA(client2, "10.0.0.16");
+ });
+
+ // Now, let's reconfigure the server to also apply restrictions on the
+ // subnet to which client2 now belongs.
+ configure(NETWORKS_CONFIG[3], *client1.getServer());
+
+ // The client should be refused to renew the lease because it doesn't belong
+ // to "b-devices" class.
+ client2.setState(Dhcp4Client::RENEWING);
+ testAssigned([this, &client2] {
+ doRequest(client2, "");
+ });
+
+ // If we add option93 with a value matching this class, the lease should
+ // get renewed.
+ OptionPtr option93_bis(new OptionUint16(Option::V4, 93, 0x0002));
+ client2.addExtraOption(option93_bis);
+
+ testAssigned([this, &client2] {
+ doRequest(client2, "10.0.0.16");
+ });
+}
+
+// IPv4 address reservation exists in one of the subnets within
+// shared network. This test also verifies that conflict resolution for
+// reserved addresses is working properly in case of shared networks.
+TEST_F(Dhcpv4SharedNetworkTest, reservationInSharedNetwork) {
+ // Create client #1. Explicitly set client's MAC address to the one that
+ // has a reservation in the first subnet within shared network.
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ client1.useRelay(true, IOAddress("192.3.5.6"));
+ client1.setHWAddress("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.
+ configure(NETWORKS_CONFIG[4], *client1.getServer());
+
+ // Client #1 should get his reserved address from the second subnet.
+ testAssigned([this, &client1] {
+ doDORA(client1, "10.0.0.29", "192.0.2.28");
+ });
+
+ // Create client #2
+ Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+ client2.useRelay(true, IOAddress("192.3.5.6"));
+ client2.setHWAddress("aa:bb:cc:dd:ee:ff");
+
+ // Client #2 should get its reserved address from the first subnet.
+ testAssigned([this, &client2] {
+ doDORA(client2, "192.0.2.28");
+ });
+
+ // Reconfigure the server. Now, the first client gets second client's
+ // reservation and vice versa.
+ configure(NETWORKS_CONFIG[5], *client1.getServer());
+
+ // The first client is trying to renew the lease and should get a DHCPNAK.
+ client1.setState(Dhcp4Client::RENEWING);
+ testAssigned([this, &client1] {
+ doRequest(client1, "");
+ });
+
+ // Similarly, the second client is trying to renew the lease and should
+ // get a DHCPNAK.
+ client2.setState(Dhcp4Client::RENEWING);
+ testAssigned([this, &client2] {
+ doRequest(client2, "");
+ });
+
+ // But the client should get a lease, if it does 4-way exchange. However, it
+ // must not get any of the reserved addresses because one of them is reserved
+ // for another client and for another one there is a valid lease.
+ client1.setState(Dhcp4Client::SELECTING);
+ testAssigned([this, &client1] {
+ ASSERT_NO_THROW(doDORA(client1, "192.0.2.29", "192.0.2.29"));
+ });
+ Pkt4Ptr resp1 = client1.getContext().response_;
+ ASSERT_TRUE(resp1);
+ EXPECT_EQ(DHCPACK, resp1->getType());
+ EXPECT_NE("10.0.0.29", resp1->getYiaddr().toText());
+ EXPECT_NE("192.0.2.28", resp1->getYiaddr().toText());
+ // Ensure stats are being recorded for HR conflicts
+ ObservationPtr subnet_conflicts = StatsMgr::instance().getObservation(
+ "subnet[10].v4-reservation-conflicts");
+ ASSERT_TRUE(subnet_conflicts);
+ ASSERT_EQ(1, subnet_conflicts->getInteger().first);
+ subnet_conflicts = StatsMgr::instance().getObservation("v4-reservation-conflicts");
+ ASSERT_TRUE(subnet_conflicts);
+ ASSERT_EQ(1, subnet_conflicts->getInteger().first);
+
+ // Client #2 is now doing 4-way exchange and should get its newly reserved
+ // address, released by the 4-way transaction of client 1.
+ client2.setState(Dhcp4Client::SELECTING);
+ testAssigned([this, &client2] {
+ doDORA(client2, "10.0.0.29");
+ });
+
+ // Same for client #1.
+ client1.setState(Dhcp4Client::SELECTING);
+ testAssigned([this, &client1] {
+ doDORA(client1, "192.0.2.28");
+ });
+}
+
+// Two clients use the same circuit ID and this circuit ID is used to assign a
+// host reservation. The clients compete for the reservation, and one of them
+// gets it and the other one gets an address from the dynamic pool. This test
+// checks that the assigned leases have appropriate subnet IDs. Previously, the
+// client getting the lease from the dynamic pool had a wrong subnet ID (not
+// matching the address from the dynamic pool).
+TEST_F(Dhcpv4SharedNetworkTest, reservationInSharedNetworkTwoClientsSameIdentifier) {
+ // Create client #1 which uses a circuit ID as a host identifier.
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ client1.useRelay(true, IOAddress("192.3.5.6"));
+ client1.includeClientId("01:02:03:04");
+ client1.setCircuitId("charter950");
+
+ // Create server configuration with a shared network including two subnets.
+ // One of the subnets includes a reservation identified by the client's
+ // circuit ID.
+ configure(NETWORKS_CONFIG[20], *client1.getServer());
+
+ // Client #1 should get the reserved address.
+ testAssigned([this, &client1] {
+ doDORA(client1, "192.0.2.28", "");
+ });
+
+ // Create client #2 with the same circuit ID.
+ Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+ client2.useRelay(true, IOAddress("192.3.5.6"));
+ client2.includeClientId("02:03:04:05");
+ client2.setCircuitId("charter950");
+
+ // Client #2 presents the same circuit ID but the reserved address has been
+ // already allocated. The client should get an address from the dynamic pool.
+ testAssigned([this, &client2] {
+ doDORA(client2, "10.0.0.1", "192.0.2.28");
+ });
+
+ // Client #1 renews the lease.
+ client1.setState(Dhcp4Client::RENEWING);
+ testAssigned([this, &client1]() {
+ doRequest(client1, "192.0.2.28");
+ });
+
+ // Client #2 renews the lease.
+ client2.setState(Dhcp4Client::RENEWING);
+ // Check if the client successfully renewed its address and that the
+ // subnet id in the renewed lease is not messed up.
+ testAssigned([this, &client2]() {
+ doRequest(client2, "10.0.0.1");
+ });
+
+ // Ensure stats are being recorded for HR conflicts
+ ObservationPtr subnet_conflicts = StatsMgr::instance().getObservation(
+ "subnet[10].v4-reservation-conflicts");
+ ASSERT_TRUE(subnet_conflicts);
+ ASSERT_EQ(1, subnet_conflicts->getInteger().first);
+ subnet_conflicts = StatsMgr::instance().getObservation("v4-reservation-conflicts");
+ ASSERT_TRUE(subnet_conflicts);
+ ASSERT_EQ(1, subnet_conflicts->getInteger().first);
+}
+
+// Reserved address can't be assigned as long as access to a subnet is
+// restricted by classification.
+TEST_F(Dhcpv4SharedNetworkTest, reservationAccessRestrictedByClass) {
+ // Create a client and set explicit MAC address for which there is a reservation
+ // in first subnet belonging to a shared network.
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.useRelay(true, IOAddress("192.3.5.6"));
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+
+ // Create configuration with a shared network including two subnets. Access to
+ // one of the subnets is restricted by client classification.
+ 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.
+ testAssigned([this, &client] {
+ doDORA(client, "10.0.0.16");
+ });
+
+ // Add option 93 which would cause the client to be classified as "a-devices".
+ OptionPtr option93(new OptionUint16(Option::V4, 93, 0x0001));
+ client.addExtraOption(option93);
+
+ // Client renews its lease and should get DHCPNAK because this client now belongs
+ // to the "a-devices" class and can be assigned a reserved address instead.
+ client.setState(Dhcp4Client::RENEWING);
+ testAssigned([this, &client] {
+ doRequest(client, "");
+ });
+
+ // Perform 4-way exchange again. It should be assigned a reserved address this time.
+ client.setState(Dhcp4Client::SELECTING);
+ testAssigned([this, &client] {
+ doDORA(client, "192.0.2.28");
+ });
+}
+
+// Some options are specified on the shared subnet level, some on the
+// subnets level.
+TEST_F(Dhcpv4SharedNetworkTest, optionsDerivation) {
+ // Client #1.
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ client1.setIfaceName("eth1");
+ client1.setIfaceIndex(ETH1_INDEX);
+ client1.requestOptions(DHO_LOG_SERVERS, DHO_COOKIE_SERVERS, DHO_DOMAIN_NAME_SERVERS);
+
+ configure(NETWORKS_CONFIG[7], *client1.getServer());
+
+ // Client #1 belongs to shared network. By providing a hint "192.0.2.63" we force
+ // the server to select first subnet within the shared network for this client.
+ doDORA(client1, "192.0.2.63", "192.0.2.63");
+
+ // This option is specified at the global level.
+ ASSERT_EQ(1, client1.config_.log_servers_.size());
+ EXPECT_EQ("1.2.3.4", client1.config_.log_servers_[0].toText());
+
+ // This option is specified on the subnet level.
+ ASSERT_EQ(1, client1.config_.routers_.size());
+ EXPECT_EQ("192.0.2.5", client1.config_.routers_[0].toText());
+
+ // This option is specified on the shared network level and the subnet level.
+ // The instance on the subnet level should take precedence.
+ ASSERT_EQ(1, client1.config_.quotes_servers_.size());
+ EXPECT_EQ("10.5.4.3", client1.config_.quotes_servers_[0].toText());
+
+ // This option is only specified on the shared network level and should be
+ // inherited by all subnets within this network.
+ ASSERT_EQ(1, client1.config_.dns_servers_.size());
+ EXPECT_EQ("10.1.2.3", client1.config_.dns_servers_[0].toText());
+
+ // Client #2.
+ Dhcp4Client client2(Dhcp4Client::SELECTING);
+ client2.setIfaceName("eth1");
+ client2.setIfaceIndex(ETH1_INDEX);
+ client2.requestOptions(DHO_LOG_SERVERS, DHO_COOKIE_SERVERS, DHO_DOMAIN_NAME_SERVERS);
+
+ // Request an address from the second subnet within the shared network.
+ doDORA(client2, "10.0.0.16", "10.0.0.16");
+
+ // This option is specified at the global level.
+ ASSERT_EQ(1, client2.config_.log_servers_.size());
+ EXPECT_EQ("1.2.3.4", client2.config_.log_servers_[0].toText());
+
+ // This option is only specified on the shared network level and should be
+ // inherited by all subnets within this network.
+ ASSERT_EQ(1, client2.config_.quotes_servers_.size());
+ EXPECT_EQ("10.6.5.4", client2.config_.quotes_servers_[0].toText());
+
+ // This option is only specified on the shared network level and should be
+ // inherited by all subnets within this network.
+ ASSERT_EQ(1, client2.config_.dns_servers_.size());
+ EXPECT_EQ("10.1.2.3", client2.config_.dns_servers_[0].toText());
+
+ // Client #3.
+ Dhcp4Client client3(Dhcp4Client::SELECTING);
+ client3.setIfaceName("eth0");
+ client3.setIfaceIndex(ETH0_INDEX);
+ client3.requestOptions(DHO_LOG_SERVERS, DHO_COOKIE_SERVERS, DHO_DOMAIN_NAME_SERVERS);
+
+ // Client 3 should get an address from the subnet defined outside of the shared network.
+ doDORA(client3, "192.0.2.65");
+
+ // This option is specified at the global level.
+ ASSERT_EQ(1, client3.config_.log_servers_.size());
+ EXPECT_EQ("1.2.3.4", client3.config_.log_servers_[0].toText());
+
+ // This option is specified on the subnet level.
+ ASSERT_EQ(1, client3.config_.quotes_servers_.size());
+ EXPECT_EQ("10.1.1.1", client3.config_.quotes_servers_[0].toText());
+
+ // This option is only specified on the shared network level and thus it should
+ // not be returned to this client, because the client doesn't belong to the
+ // shared network.
+ ASSERT_EQ(0, client3.config_.dns_servers_.size());
+}
+
+// Client has a lease in a subnet within shared network.
+TEST_F(Dhcpv4SharedNetworkTest, initReboot) {
+ // Create client #1.
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ client1.setIfaceName("eth1");
+ client1.setIfaceIndex(ETH1_INDEX);
+
+ configure(NETWORKS_CONFIG[0], *client1.getServer());
+
+ // Perform 4-way exchange to obtain a lease. The client should get the lease from
+ // the second subnet.
+ testAssigned([this, &client1] {
+ doDORA(client1, "10.0.0.16", "10.0.0.16");
+ });
+
+ // The client1 transitions to INIT-REBOOT state in which the client1 remembers the
+ // lease and sends DHCPREQUEST to all servers (server id) is not specified. If
+ // the server doesn't know the client1 (doesn't have its lease), it should
+ // drop the request. We want to make sure that the server responds (resp1) regardless
+ // of the subnet from which the lease has been allocated.
+ client1.setState(Dhcp4Client::INIT_REBOOT);
+ testAssigned([this, &client1] {
+ doRequest(client1, "10.0.0.16");
+ });
+
+ // Create client #2.
+ Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+ client2.setIfaceName("eth1");
+ client2.setIfaceIndex(ETH1_INDEX);
+
+ // Let's make sure that the behavior is the same for the other subnet within the
+ // same shared network.
+ testAssigned([this, &client2] {
+ doDORA(client2, "192.0.2.63", "192.0.2.63");
+ });
+
+ // The client2 transitions to INIT-REBOOT state in which the client2 remembers the
+ // lease and sends DHCPREQUEST to all servers (server id) is not specified. If
+ // the server doesn't know the client2 (doesn't have its lease), it should
+ // drop the request. We want to make sure that the server responds (resp2) regardless
+ // of the subnet from which the lease has been allocated.
+ client2.setState(Dhcp4Client::INIT_REBOOT);
+ testAssigned([this, &client2] {
+ doRequest(client2, "192.0.2.63");
+ });
+}
+
+// Host reservations include hostname, next server and client class.
+TEST_F(Dhcpv4SharedNetworkTest, variousFieldsInReservation) {
+ // Create client.
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.setIfaceName("eth1");
+ client.setIfaceIndex(ETH1_INDEX);
+ client.setHWAddress("11:22:33:44:55:66");
+
+ // Include hostname to force the server to return hostname to
+ // the client.
+ client.includeHostname("my.example.org");
+
+ // Configure the server with a shared network including two subnets.
+ // The client has address/hostname reservation in the second subnet.
+ configure(NETWORKS_CONFIG[10], *client.getServer());
+
+ // Perform 4-way exchange.
+ testAssigned([&client] {
+ ASSERT_NO_THROW(client.doDORA());
+ });
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ EXPECT_EQ(DHCPACK, resp->getType());
+ EXPECT_EQ("10.0.0.29", resp->getYiaddr().toText());
+
+ // The client should get a hostname from the reservation, rather than
+ // the hostname 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 hostname. This subnet has no reservation so it would
+ // return the same hostname that the client has sent. We expect
+ // that the hostname being sent is the one that is incldued in the
+ // reservations.
+ OptionStringPtr hostname;
+ hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
+ ASSERT_TRUE(hostname);
+ EXPECT_EQ("test.example.org", hostname->getValue());
+
+ // The next server value should also be set according to the settings
+ // in host reservations.
+ EXPECT_EQ("10.10.10.10", resp->getSiaddr().toText());
+
+ // The boot-file-name value should be derived from the client class
+ // based on the static class reservations.
+ const std::string expected_fname = "/dev/null";
+ const OptionBuffer fname = resp->getFile();
+ const std::string converted_fname(fname.cbegin(),
+ fname.cbegin() + expected_fname.size());
+ EXPECT_EQ(expected_fname, converted_fname);
+}
+
+// Different shared network is selected for different local interface.
+TEST_F(Dhcpv4SharedNetworkTest, sharedNetworkSelectionByInterface) {
+ // Create client #1. The server receives requests from this client
+ // via interface eth1 and should assign shared network "frog" for
+ // this client.
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ client1.setIfaceName("eth1");
+ client1.setIfaceIndex(ETH1_INDEX);
+
+ // Create server configuration with two shared networks selected
+ // by the local interface: eth1 and eth0.
+ configure(NETWORKS_CONFIG[8], *client1.getServer());
+
+ // Perform 4-way exchange.
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doDORA());
+ });
+ Pkt4Ptr resp1 = client1.getContext().response_;
+ ASSERT_TRUE(resp1);
+ EXPECT_EQ(DHCPACK, resp1->getType());
+ // The client should be assigned an address from the 192.0.2.X
+ // address range.
+ EXPECT_EQ("192.0.2", resp1->getYiaddr().toText().substr(0, 7));
+
+ // Create client #2 which requests are received on eth0.
+ Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+ client2.setIfaceName("eth0");
+ client2.setIfaceIndex(ETH0_INDEX);
+
+ // Perform 4-way exchange.
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doDORA());
+ });
+ Pkt4Ptr resp2 = client2.getContext().response_;
+ ASSERT_TRUE(resp2);
+ EXPECT_EQ(DHCPACK, resp2->getType());
+ // The client should be assigned an address from the 10.0.0.X
+ // address range.
+ EXPECT_EQ("10.0.0", resp2->getYiaddr().toText().substr(0, 6));
+}
+
+// Different shared network is selected for different relay address.
+TEST_F(Dhcpv4SharedNetworkTest, sharedNetworkSelectionByRelay) {
+ // Create relayed client #1.
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ client1.useRelay(true, IOAddress("10.1.2.3"));
+
+ // Create server configuration with two shared networks selected
+ // by the relay address.
+ configure(NETWORKS_CONFIG[9], *client1.getServer());
+
+ // Perform 4-way exchange.
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doDORA());
+ });
+ Pkt4Ptr resp1 = client1.getContext().response_;
+ ASSERT_TRUE(resp1);
+ EXPECT_EQ(DHCPACK, resp1->getType());
+ // The client should be assigned an address from the 192.0.2.X
+ // address range.
+ EXPECT_EQ("192.0.2", resp1->getYiaddr().toText().substr(0, 7));
+
+ // Create relayed client #2.
+ Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+ client2.useRelay(true, IOAddress("192.1.2.3"));
+
+ // Perform 4-way exchange.
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doDORA());
+ });
+ Pkt4Ptr resp2 = client2.getContext().response_;
+ ASSERT_TRUE(resp2);
+ EXPECT_EQ(DHCPACK, resp2->getType());
+ // The client should be assigned an address from the 10.0.0.X
+ // address range.
+ EXPECT_EQ("10.0.0", resp2->getYiaddr().toText().substr(0, 6));
+}
+
+// Client id matching gets disabled on the shared network level.
+TEST_F(Dhcpv4SharedNetworkTest, matchClientId) {
+ // Create client using client identifier besides MAC address.
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.includeClientId("01:02:03:04");
+ client.setIfaceName("eth1");
+ client.setIfaceIndex(ETH1_INDEX);
+
+ // Create server configuration with match-client-id value initially
+ // set to true. The client should be allocated a lease and the
+ // client identifier should be included in this lease.
+ configure(NETWORKS_CONFIG[11], *client.getServer());
+
+ // Perform 4-way exchange.
+ testAssigned([&client] {
+ ASSERT_NO_THROW(client.doDORA());
+ });
+ Pkt4Ptr resp1 = client.getContext().response_;
+ ASSERT_TRUE(resp1);
+ ASSERT_EQ(DHCPACK, resp1->getType());
+
+ // Reconfigure the server and turn off client identifier matching
+ // on the shared network level. The subnet from which the client
+ // is allocated an address should derive the match-client-id value
+ // and ignore the fact that the client identifier is not matching.
+ configure(NETWORKS_CONFIG[12], *client.getServer());
+
+ client.includeClientId("01:01:01:01");
+ client.setState(Dhcp4Client::RENEWING);
+
+ // Try to renew the lease with modified MAC address.
+ testAssigned([&client] {
+ ASSERT_NO_THROW(client.doRequest());
+ });
+ Pkt4Ptr resp2 = client.getContext().response_;
+ ASSERT_TRUE(resp2);
+ ASSERT_EQ(DHCPACK, resp2->getType());
+
+ // The lease should get renewed.
+ EXPECT_EQ(resp2->getYiaddr().toText(), resp1->getYiaddr().toText());
+}
+
+// Shared network is selected based on the client class specified.
+TEST_F(Dhcpv4SharedNetworkTest, sharedNetworkSelectedByClass) {
+ // Create client #1.
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ client1.setIfaceName("eth1");
+ client1.setIfaceIndex(ETH1_INDEX);
+
+ // Add option93 which would cause the client1 to be classified as "b-devices".
+ OptionPtr option93(new OptionUint16(Option::V4, 93, 0x0002));
+ client1.addExtraOption(option93);
+
+ // Configure the server with two shared networks which can be accessed
+ // by clients belonging to "a-devices" and "b-devices" classes
+ // respectively.
+ configure(NETWORKS_CONFIG[13], *client1.getServer());
+
+ // Simply send DHCPDISCOVER to avoid allocating a lease.
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doDiscover());
+ });
+ Pkt4Ptr resp1 = client1.getContext().response_;
+ ASSERT_TRUE(resp1);
+ ASSERT_EQ(DHCPOFFER, resp1->getType());
+ // The client should be offered a lease from the second shared network.
+ EXPECT_EQ("10.0.0.63", resp1->getYiaddr().toText());
+
+ // Create another client which will belong to a different class.
+ Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+ client2.setIfaceName("eth1");
+ client2.setIfaceIndex(ETH1_INDEX);
+
+ // Add option93 which would cause the client1 to be classified as "a-devices".
+ option93.reset(new OptionUint16(Option::V4, 93, 0x0001));
+ client2.addExtraOption(option93);
+
+ // Send DHCPDISCOVER. There is no lease in the lease database so the
+ // client should be offered a lease based on the client class selection.
+ testAssigned([this, &client2] {
+ doDiscover(client2, "192.0.2.63", "");
+ });
+}
+
+// This test verifies that custom server identifier can be specified for a
+// shared network.
+TEST_F(Dhcpv4SharedNetworkTest, customServerIdentifier) {
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ client1.setIfaceName("eth1");
+ client1.setIfaceIndex(ETH1_INDEX);
+
+ // Configure DHCP server.
+ ASSERT_NO_THROW(configure(NETWORKS_CONFIG[15], *client1.getServer()));
+
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doDORA());
+ });
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client1.getContext().response_);
+ Pkt4Ptr resp = client1.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // The explicitly configured server identifier should take precedence
+ // over generated server identifier.
+ EXPECT_EQ("1.2.3.4", client1.config_.serverid_.toText());
+
+ // Create another client using different interface.
+ Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+ client2.setIfaceName("eth0");
+ client2.setIfaceIndex(ETH0_INDEX);
+
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doDORA());
+ });
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client2.getContext().response_);
+ resp = client2.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // The explicitly configured server identifier should take precedence
+ // over generated server identifier.
+ EXPECT_EQ("2.3.4.5", client2.config_.serverid_.toText());
+}
+
+// Access to a pool within shared network is restricted by client
+// classification.
+TEST_F(Dhcpv4SharedNetworkTest, poolInSharedNetworkSelectedByClass) {
+ // Create client #1
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ client1.setIfaceName("eth1");
+ client1.setIfaceIndex(ETH1_INDEX);
+
+ // Configure the server with one shared network including one subnet and
+ // in 2 pools in it. The access to one of the pools is restricted
+ // by client classification.
+ configure(NETWORKS_CONFIG[16], *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.
+ testAssigned([this, &client1] {
+ doDORA(client1, "192.0.2.100", "192.0.2.63");
+ });
+
+ // 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 option93 which would cause the client to be classified as "a-devices".
+ OptionPtr option93(new OptionUint16(Option::V4, 93, 0x0001));
+ client1.addExtraOption(option93);
+
+ // This time, the allocation of the address provided as hint should be successful.
+ testAssigned([this, &client1] {
+ doDORA(client1, "192.0.2.63", "192.0.2.63");
+ });
+
+ // Client 2 should be assigned an address from the unrestricted pool.
+ Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+ client2.setIfaceName("eth1");
+ client2.setIfaceIndex(ETH1_INDEX);
+ testAssigned([this, &client2] {
+ doDORA(client2, "192.0.2.100");
+ });
+
+ // Now, let's reconfigure the server to also apply restrictions on the
+ // pool to which client2 now belongs.
+ configure(NETWORKS_CONFIG[17], *client1.getServer());
+
+ // The client should be refused to renew the lease because it doesn't belong
+ // to "b-devices" class.
+ client2.setState(Dhcp4Client::RENEWING);
+ testAssigned([this, &client2] {
+ doRequest(client2, "");
+ });
+
+ // If we add option93 with a value matching this class, the lease should
+ // get renewed.
+ OptionPtr option93_bis(new OptionUint16(Option::V4, 93, 0x0002));
+ client2.addExtraOption(option93_bis);
+
+ testAssigned([this, &client2] {
+ doRequest(client2, "192.0.2.100");
+ });
+}
+
+// Access to a pool within plain subnet is restricted by client classification.
+TEST_F(Dhcpv4SharedNetworkTest, poolInSubnetSelectedByClass) {
+ // Create client #1
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ client1.setIfaceName("eth1");
+ client1.setIfaceIndex(ETH1_INDEX);
+
+ // Configure the server with one plain subnet including two pools.
+ // The access to one of the pools is restricted by client classification.
+ configure(NETWORKS_CONFIG[18], *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.
+ testAssigned([this, &client1] {
+ doDORA(client1, "192.0.2.100", "192.0.2.63");
+ });
+
+ // 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 option93 which would cause the client to be classified as "a-devices".
+ OptionPtr option93(new OptionUint16(Option::V4, 93, 0x0001));
+ client1.addExtraOption(option93);
+
+ // This time, the allocation of the address provided as hint should be successful.
+ testAssigned([this, &client1] {
+ doDORA(client1, "192.0.2.63", "192.0.2.63");
+ });
+
+ // Client 2 should be assigned an address from the unrestricted pool.
+ Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+ client2.setIfaceName("eth1");
+ client2.setIfaceIndex(ETH1_INDEX);
+ testAssigned([this, &client2] {
+ doDORA(client2, "192.0.2.100");
+ });
+
+ // Now, let's reconfigure the server to also apply restrictions on the
+ // pool to which client2 now belongs.
+ configure(NETWORKS_CONFIG[19], *client1.getServer());
+
+ // The client should be refused to renew the lease because it doesn't belong
+ // to "b-devices" class.
+ client2.setState(Dhcp4Client::RENEWING);
+ testAssigned([this, &client2] {
+ doRequest(client2, "");
+ });
+
+ // If we add option93 with a value matching this class, the lease should
+ // get renewed.
+ OptionPtr option93_bis(new OptionUint16(Option::V4, 93, 0x0002));
+ client2.addExtraOption(option93_bis);
+
+ testAssigned([this, &client2] {
+ doRequest(client2, "192.0.2.100");
+ });
+}
+
+// Shared network is selected based on giaddr value (relay specified
+// on shared network level, but response is send to source address.
+TEST_F(Dhcpv4SharedNetworkTest, sharedNetworkSendToSourceTestingModeEnabled) {
+ // Create client #1. This is a relayed client which is using relay
+ // address matching configured shared network.
+ // Source address is set to unrelated to configuration.
+
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ // Put Kea into testing mode.
+ client1.getServer()->setSendResponsesToSource(true);
+ client1.useRelay(true, IOAddress("192.3.5.6"), IOAddress("1.1.1.2"));
+ // Configure the server with one shared network and one subnet outside of the
+ // shared network.
+ configure(NETWORKS_CONFIG[1], *client1.getServer());
+ // Client #1 should be assigned an address from shared network.
+ testAssigned([this, &client1] {
+ doDORA(client1, "192.0.2.63", "192.0.2.63");
+ });
+
+ // normally Kea would send packet to 192.3.5.6 but we want it get from
+ // 1.1.1.2 in send to source testing mode but still with correctly
+ // assigned address.
+ Pkt4Ptr resp1 = client1.getContext().response_;
+ EXPECT_EQ("1.1.1.2", resp1->getLocalAddr().toText());
+
+ // Create client #2. This is a relayed client which is using relay
+ // address matching subnet outside of the shared network.
+ Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+ client2.useRelay(true, IOAddress("192.1.2.3"), IOAddress("2.2.2.3"));
+ testAssigned([this, &client2] {
+ doDORA(client2, "192.0.2.65", "192.0.2.63");
+ });
+
+ Pkt4Ptr resp2 = client2.getContext().response_;
+ EXPECT_EQ("2.2.2.3", resp2->getLocalAddr().toText());
+ // reset testing mode.
+ client1.getServer()->setSendResponsesToSource(false);
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pool < host reservation
+TEST_F(Dhcpv4SharedNetworkTest, precedenceGlobal) {
+ const std::string config =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.1\""
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"192.0.2.28\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "192.0.2.1");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pool < host reservation
+TEST_F(Dhcpv4SharedNetworkTest, precedenceClass) {
+ const std::string config =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"192.0.2.28\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "192.0.2.2");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pool < host reservation
+TEST_F(Dhcpv4SharedNetworkTest, precedenceClasses) {
+ const std::string config =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"beta\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.2\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.3\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"192.0.2.28\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ // Class order is the insert order
+ testPrecedence(config, "192.0.2.2");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pool < host reservation
+TEST_F(Dhcpv4SharedNetworkTest, precedenceNetwork) {
+ const std::string config =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.3\""
+ " }"
+ " ],"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"192.0.2.28\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "192.0.2.3");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pool < host reservation
+TEST_F(Dhcpv4SharedNetworkTest, precedenceSubnet) {
+ const std::string config =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.3\""
+ " }"
+ " ],"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.4\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"192.0.2.28\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "192.0.2.4");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pool < host reservation
+TEST_F(Dhcpv4SharedNetworkTest, precedencePool) {
+ const std::string config =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.3\""
+ " }"
+ " ],"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.4\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.5\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"192.0.2.28\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "192.0.2.5");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pool < host reservation
+TEST_F(Dhcpv4SharedNetworkTest, precedenceReservation) {
+ const std::string config =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.3\""
+ " }"
+ " ],"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.4\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.5\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"192.0.2.28\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.6\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "192.0.2.6");
+}
+
+// Verify authoritative sanitization.
+// This test generates many similar configs. They all have similar
+// structure:
+// - 1 shared-network (with possibly authoritative flag in it)
+// 2 subnets (with each possibly having its own authoritative flag)
+//
+// If the flag is specified on subnet-level, the value must match those
+// specified on subnet level.
+TEST_F(Dhcpv4SharedNetworkTest, authoritative) {
+
+ // Each scenario will be defined using those parameters.
+ typedef struct scenario {
+ bool exp_success;
+ AuthoritativeFlag global;
+ AuthoritativeFlag subnet1;
+ AuthoritativeFlag subnet2;
+ } scenario;
+
+ // We have the following scenarios. The default is no.
+ // The only allowed combinations are those that end up with
+ // having all three values (global, subnet1, subnet2) agree.
+ scenario scenarios[] = {
+ //result, global, subnet1, subnet2
+ { true, AUTH_DEFAULT, AUTH_DEFAULT, AUTH_DEFAULT },
+ { true, AUTH_YES, AUTH_DEFAULT, AUTH_DEFAULT },
+ { true, AUTH_YES, AUTH_YES, AUTH_YES },
+ { true, AUTH_NO, AUTH_DEFAULT, AUTH_DEFAULT },
+ { true, AUTH_NO, AUTH_NO, AUTH_NO },
+ { false, AUTH_DEFAULT, AUTH_YES, AUTH_NO },
+ { false, AUTH_DEFAULT, AUTH_NO, AUTH_YES },
+ { false, AUTH_DEFAULT, AUTH_YES, AUTH_YES },
+ { false, AUTH_YES, AUTH_NO, AUTH_NO },
+ { false, AUTH_YES, AUTH_DEFAULT, AUTH_NO },
+ { false, AUTH_YES, AUTH_NO, AUTH_DEFAULT },
+ { false, AUTH_YES, AUTH_NO, AUTH_NO },
+ { false, AUTH_YES, AUTH_NO, AUTH_YES },
+ { false, AUTH_YES, AUTH_YES, AUTH_NO }
+ };
+
+ // Let's test them one by one
+ int cnt = 0;
+ for ( auto s : scenarios) {
+ cnt++;
+
+ string cfg = generateAuthConfig(s.global, s.subnet1, s.subnet2);
+
+ // Create client and set MAC address to the one that has a reservation.
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ stringstream tmp;
+ tmp << "Testing scenario " << cnt;
+ SCOPED_TRACE(tmp.str());
+ // Create server configuration.
+ auto result = configureWithStatus(cfg, *client.getServer(), true, s.exp_success? 0 : 1);
+ if (s.exp_success) {
+ EXPECT_EQ(result.first, 0) << result.second;
+ } else {
+ EXPECT_EQ(result.first, 1) << "Configuration expected to fail, but succeeded";
+ }
+ }
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/simple_parser4_unittest.cc b/src/bin/dhcp4/tests/simple_parser4_unittest.cc
new file mode 100644
index 0000000..6ac157a
--- /dev/null
+++ b/src/bin/dhcp4/tests/simple_parser4_unittest.cc
@@ -0,0 +1,215 @@
+// Copyright (C) 2016-2019 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_parser4.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <cc/data.h>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief DHCP Parser test fixture class
+class SimpleParser4Test : 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);
+
+ // 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);
+
+ // 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);
+
+ // 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());
+ }
+};
+
+// This test checks if global defaults are properly set for DHCPv4.
+TEST_F(SimpleParser4Test, globalDefaults4) {
+
+ ElementPtr empty = parseJSON("{ }");
+ size_t num = 0;
+
+ EXPECT_NO_THROW(num = SimpleParser4::setAllDefaults(empty));
+
+
+ // We expect at least 1 parameter to be inserted.
+ EXPECT_TRUE(num >= 1);
+
+ checkIntegerValue(empty, "valid-lifetime", 7200);
+
+ // Timers are optional and by default are not present
+ EXPECT_FALSE(empty->contains("rebind-timer"));
+ EXPECT_FALSE(empty->contains("renew-timer"));
+
+ // Make sure that preferred-lifetime is not set for v4 (it's v6 only
+ // parameter)
+ EXPECT_FALSE(empty->get("preferred-lifetime"));
+}
+
+// This test checks if the parameters can be inherited from the global
+// scope to the subnet scope.
+TEST_F(SimpleParser4Test, inheritGlobalToSubnet4) {
+ ElementPtr global = parseJSON("{ \"renew-timer\": 1,"
+ " \"rebind-timer\": 2,"
+ " \"valid-lifetime\": 4,"
+ " \"min-valid-lifetime\": 3,"
+ " \"max-valid-lifetime\": 5,"
+ " \"subnet4\": [ { \"renew-timer\": 100 } ] "
+ "}");
+ ConstElementPtr subnets = global->find("subnet4");
+ ASSERT_TRUE(subnets);
+ ConstElementPtr subnet = subnets->get(0);
+ ASSERT_TRUE(subnet);
+
+ // we should inherit 4 parameters. Renew-timer should remain intact,
+ // as it was already defined in the subnet scope.
+ size_t num;
+ EXPECT_NO_THROW(num = SimpleParser4::deriveParameters(global));
+ EXPECT_EQ(4, num);
+
+ // Check the values. 2 of them are inherited, while the third one
+ // was already defined in the subnet, so should not be inherited.
+ checkIntegerValue(subnet, "renew-timer", 100);
+ checkIntegerValue(subnet, "rebind-timer", 2);
+ checkIntegerValue(subnet, "valid-lifetime", 4);
+ checkIntegerValue(subnet, "min-valid-lifetime", 3);
+ checkIntegerValue(subnet, "max-valid-lifetime", 5);
+}
+
+// This test checks if the parameters in "subnet4" are assigned default values
+// if not explicitly specified.
+TEST_F(SimpleParser4Test, subnetDefaults4) {
+ ElementPtr global = parseJSON("{ \"renew-timer\": 1,"
+ " \"rebind-timer\": 2,"
+ " \"valid-lifetime\": 4,"
+ " \"subnet4\": [ { } ] "
+ "}");
+
+ size_t num = 0;
+ EXPECT_NO_THROW(num = SimpleParser4::setAllDefaults(global));
+ EXPECT_LE(1, num); // at least 1 parameter has to be modified
+
+ ConstElementPtr subnets = global->find("subnet4");
+ 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(SimpleParser4Test, optionDataDefaults4) {
+ ElementPtr global = parseJSON("{ \"renew-timer\": 1,"
+ " \"rebind-timer\": 2,"
+ " \"valid-lifetime\": 4,"
+ " \"option-data\": [ { } ] "
+ "}");
+
+ size_t num = 0;
+ EXPECT_NO_THROW(num = SimpleParser4::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
+ // SimpleParser4::OPTION4_DEFAULTS for a list of default values.
+ checkStringValue(option, "space", "dhcp4");
+ checkBoolValue(option, "csv-format", true);
+}
+
+// This test checks if the parameters in option-data are assigned default values
+// if not explicitly specified.
+TEST_F(SimpleParser4Test, optionDefDefaults4) {
+ ElementPtr global = parseJSON("{ "
+ " \"option-def\": [ { } ] "
+ "}");
+
+ size_t num = 0;
+ EXPECT_NO_THROW(num = SimpleParser4::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
+ // SimpleParser4::OPTION4_DEFAULTS for a list of default values.
+ checkStringValue(def, "record-types", "");
+ checkStringValue(def, "space", "dhcp4");
+ checkStringValue(def, "encapsulate", "");
+ checkBoolValue(def, "array", false);
+}
+
+};
+};
+};
diff --git a/src/bin/dhcp4/tests/test_data_files_config.h.in b/src/bin/dhcp4/tests/test_data_files_config.h.in
new file mode 100644
index 0000000..dfa1e64
--- /dev/null
+++ b/src/bin/dhcp4/tests/test_data_files_config.h.in
@@ -0,0 +1,9 @@
+// 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/.
+
+/// @brief Path to dhcp4 source dir so tests against the dhcp4.spec file
+/// can find it reliably.
+#define DHCP4_SRC_DIR "@abs_top_srcdir@/src/bin/dhcp4"
diff --git a/src/bin/dhcp4/tests/test_libraries.h.in b/src/bin/dhcp4/tests/test_libraries.h.in
new file mode 100644
index 0000000..9b9a243
--- /dev/null
+++ b/src/bin/dhcp4/tests/test_libraries.h.in
@@ -0,0 +1,32 @@
+// 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 {
+
+#define DLL_SUFFIX ".so"
+
+// 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/dhcp4/tests/vendor_opts_unittest.cc b/src/bin/dhcp4/tests/vendor_opts_unittest.cc
new file mode 100644
index 0000000..08f446d
--- /dev/null
+++ b/src/bin/dhcp4/tests/vendor_opts_unittest.cc
@@ -0,0 +1,1641 @@
+// Copyright (C) 2019-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/.
+
+// This file is dedicated to testing vendor options. There are several
+// vendor options in DHCPv4:
+//
+// vivso (125) - vendor independent vendor specific option. This is by far the
+// most popular
+// vendor specific (43) - this is probably the second most popular.
+// Unfortunately, its definition is blurry, so there are many
+// similar, but not exact implementations that do things in
+// different ways.
+// vivco (124) - vendor independent vendor class option.
+// class identifier (60) - not exactly vendor specific. It's a string, but the
+// content of that string identifies what kind of vendor device
+// this is.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <cc/command_interpreter.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp4/dhcp4_srv.h>
+#include <dhcp4/json_config_parser.h>
+#include <dhcp4/tests/dhcp4_client.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/tests/pkt_captures.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/dhcp4.h>
+#include <dhcpsrv/cfgmgr.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::config;
+using namespace isc::dhcp::test;
+
+/// @brief Class dedicated to testing vendor options in DHCPv4
+///
+/// For the time being it does not provide any additional functionality, but it
+/// groups all vendor related tests under a single name. There were too many
+/// tests in Dhcpv4SrvTest class anyway.
+class VendorOptsTest : public Dhcpv4SrvTest {
+public:
+ /// @brief Called before each test
+ void SetUp() override {
+ iface_mgr_test_config_.reset(new IfaceMgrTestConfig(true));
+ IfaceMgr::instance().openSockets4();
+ }
+
+ /// @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.
+ void testVendorOptionsORO(int vendor_id) {
+ // Create a config with a custom option for Cable Labs.
+ string config = R"(
+ {
+ "interfaces-config": {
+ "interfaces": [ "*" ]
+ },
+ "option-data": [
+ {
+ "code": 2,
+ "csv-format": true,
+ "data": "192.0.2.1, 192.0.2.2",
+ "name": "tftp-servers",
+ "space": "vendor-4491"
+ }
+ ],
+ "subnet4": [
+ {
+ "interface": "eth0",
+ "pools": [
+ {
+ "pool": "192.0.2.0/25"
+ }
+ ],
+ "subnet": "192.0.2.0/24"
+ }
+ ]
+ }
+ )";
+
+ // Parse the configuration.
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+
+ // Configure a mocked server.
+ NakedDhcpv4Srv srv(0);
+ ConstElementPtr x;
+ EXPECT_NO_THROW(x = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+ CfgMgr::instance().commit();
+
+ // Set the giaddr and hops to non-zero address as if it was relayed.
+ boost::shared_ptr<Pkt4> dis(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setGiaddr(IOAddress("192.0.2.1"));
+ dis->setHops(1);
+
+ // Set interface. It is required by the server to generate server id.
+ dis->setIface("eth0");
+ dis->setIndex(ETH0_INDEX);
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ Pkt4Ptr offer = srv.processDiscover(dis);
+
+ // Check if we get a response at all.
+ ASSERT_TRUE(offer);
+
+ // We did not include any vendor opts in DISCOVER, so there should be none
+ // in OFFER.
+ ASSERT_FALSE(offer->getOption(DHO_VIVSO_SUBOPTIONS));
+
+ // 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.
+ boost::shared_ptr<OptionUint8Array> vendor_oro(new OptionUint8Array(Option::V4,
+ DOCSIS3_V4_ORO));
+ vendor_oro->addValue(DOCSIS3_V4_TFTP_SERVERS); // Request option 2.
+ OptionPtr vendor(new OptionVendor(Option::V4, vendor_id));
+ vendor->addOption(vendor_oro);
+ dis->addOption(vendor);
+
+ // Need to process DHCPDISCOVER again after requesting new option.
+ offer = srv.processDiscover(dis);
+ ASSERT_TRUE(offer);
+
+ // 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.
+ OptionPtr tmp = offer->getOption(DHO_VIVSO_SUBOPTIONS);
+ if (vendor_id != VENDOR_ID_CABLE_LABS) {
+ EXPECT_FALSE(tmp);
+ return;
+ }
+ ASSERT_TRUE(tmp);
+
+ // The response should be an OptionVendor.
+ boost::shared_ptr<OptionVendor> vendor_resp =
+ boost::dynamic_pointer_cast<OptionVendor>(tmp);
+ ASSERT_TRUE(vendor_resp);
+
+ // Option 2 should be present.
+ OptionPtr docsis2 = vendor_resp->getOption(DOCSIS3_V4_TFTP_SERVERS);
+ ASSERT_TRUE(docsis2);
+
+ // It should be an Option4AddrLst.
+ Option4AddrLstPtr tftp_srvs = boost::dynamic_pointer_cast<Option4AddrLst>(docsis2);
+ ASSERT_TRUE(tftp_srvs);
+
+ // Check that the provided addresses match the ones in configuration.
+ Option4AddrLst::AddressContainer addrs = tftp_srvs->getAddresses();
+ ASSERT_EQ(2, addrs.size());
+ EXPECT_EQ("192.0.2.1", addrs[0].toText());
+ EXPECT_EQ("192.0.2.2", addrs[1].toText());
+ }
+
+ std::unique_ptr<IfaceMgrTestConfig> iface_mgr_test_config_;
+};
+
+/// @todo Add more extensive vendor options tests, including multiple
+/// vendor options
+
+// Checks if vendor options are parsed correctly and requested vendor options
+// are echoed back.
+TEST_F(VendorOptsTest, vendorOptionsDocsis) {
+ NakedDhcpv4Srv srv(0);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ " \"option-data\": [ {"
+ " \"name\": \"tftp-servers\","
+ " \"space\": \"vendor-4491\","
+ " \"code\": 2,"
+ " \"data\": \"10.253.175.16\","
+ " \"csv-format\": true"
+ " }],"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"10.254.226.0/25\" } ],"
+ " \"subnet\": \"10.254.226.0/24\", "
+ " \"interface\": \"eth0\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Let's create a relayed DISCOVER. This particular relayed DISCOVER has
+ // added option 82 (relay agent info) with 3 suboptions. The server
+ // is supposed to echo it back in its response.
+ Pkt4Ptr dis;
+ ASSERT_NO_THROW(dis = PktCaptures::captureRelayedDiscover());
+
+ // Simulate that we have received that traffic
+ srv.fakeReceive(dis);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered buffer4_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
+ Pkt4Ptr offer = srv.fake_sent_.front();
+ ASSERT_TRUE(offer);
+
+ // Get Relay Agent Info from query...
+ OptionPtr vendor_opt_response = offer->getOption(DHO_VIVSO_SUBOPTIONS);
+ ASSERT_TRUE(vendor_opt_response);
+
+ // Check if it's of a correct type
+ boost::shared_ptr<OptionVendor> vendor_opt =
+ boost::dynamic_pointer_cast<OptionVendor>(vendor_opt_response);
+ ASSERT_TRUE(vendor_opt);
+
+ // Get Relay Agent Info from response...
+ OptionPtr tftp_servers_generic = vendor_opt->getOption(DOCSIS3_V4_TFTP_SERVERS);
+ ASSERT_TRUE(tftp_servers_generic);
+
+ Option4AddrLstPtr tftp_servers =
+ boost::dynamic_pointer_cast<Option4AddrLst>(tftp_servers_generic);
+
+ ASSERT_TRUE(tftp_servers);
+
+ Option4AddrLst::AddressContainer addrs = tftp_servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("10.253.175.16", addrs[0].toText());
+}
+
+// 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
+ Pkt4Ptr dis = PktCaptures::captureRelayedDiscover();
+ ASSERT_NO_THROW(dis->unpack());
+
+ // Check if the packet contain
+ OptionPtr opt = dis->getOption(DHO_VIVSO_SUBOPTIONS);
+ ASSERT_TRUE(opt);
+
+ boost::shared_ptr<OptionVendor> vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
+ ASSERT_TRUE(vendor);
+
+ // This particular capture that we have included options 1 and 5
+ EXPECT_TRUE(vendor->getOption(1));
+ EXPECT_TRUE(vendor->getOption(5));
+
+ // It did not include options any other options
+ EXPECT_FALSE(vendor->getOption(2));
+ EXPECT_FALSE(vendor->getOption(3));
+ 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) {
+
+ // Let's get a traffic capture from DOCSIS3.0 modem
+ Pkt4Ptr dis = PktCaptures::captureRelayedDiscover();
+ EXPECT_NO_THROW(dis->unpack());
+
+ // Check if the packet contains vendor specific information option
+ OptionPtr opt = dis->getOption(DHO_VIVSO_SUBOPTIONS);
+ ASSERT_TRUE(opt);
+
+ boost::shared_ptr<OptionVendor> vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
+ ASSERT_TRUE(vendor);
+
+ opt = vendor->getOption(DOCSIS3_V4_ORO);
+ ASSERT_TRUE(opt);
+
+ OptionUint8ArrayPtr oro = boost::dynamic_pointer_cast<OptionUint8Array>(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, vendorOptionsORO) {
+ testVendorOptionsORO(VENDOR_ID_CABLE_LABS);
+}
+
+// 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, vendorOptionsORODifferentVendorID) {
+ testVendorOptionsORO(32768);
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptions) {
+ NakedDhcpv4Srv srv(0);
+
+ ConstElementPtr x;
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ " \"option-data\": [ {"
+ " \"name\": \"tftp-servers\","
+ " \"space\": \"vendor-4491\","
+ " \"code\": 2,"
+ " \"data\": \"192.0.2.1, 192.0.2.2\","
+ " \"csv-format\": true,"
+ " \"always-send\": true"
+ " }],"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.0/25\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"interface\": \"eth0\" "
+ " } ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ boost::shared_ptr<Pkt4> dis(new Pkt4(DHCPDISCOVER, 1234));
+ // Set the giaddr and hops to non-zero address as if it was relayed.
+ dis->setGiaddr(IOAddress("192.0.2.1"));
+ dis->setHops(1);
+
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+ // Set interface. It is required by the server to generate server id.
+ dis->setIface("eth0");
+ dis->setIndex(ETH0_INDEX);
+
+ // Let's add a vendor-option (vendor-id=4491).
+ OptionPtr vendor(new OptionVendor(Option::V4, 4491));
+ dis->addOption(vendor);
+
+ // Pass it to the server and get an advertise
+ Pkt4Ptr offer = srv.processDiscover(dis);
+
+ // check if we get response at all
+ ASSERT_TRUE(offer);
+
+ // Check if there is a vendor option response
+ OptionPtr tmp = offer->getOption(DHO_VIVSO_SUBOPTIONS);
+ 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);
+
+ OptionPtr docsis2 = vendor_resp->getOption(DOCSIS3_V4_TFTP_SERVERS);
+ ASSERT_TRUE(docsis2);
+
+ Option4AddrLstPtr tftp_srvs = boost::dynamic_pointer_cast<Option4AddrLst>(docsis2);
+ ASSERT_TRUE(tftp_srvs);
+
+ Option4AddrLst::AddressContainer addrs = tftp_srvs->getAddresses();
+ ASSERT_EQ(2, addrs.size());
+ EXPECT_EQ("192.0.2.1", addrs[0].toText());
+ EXPECT_EQ("192.0.2.2", addrs[1].toText());
+}
+
+// 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\": [ ]"
+ "},"
+ " \"option-data\": [ {"
+ " \"name\": \"tftp-servers\","
+ " \"space\": \"vendor-4491\","
+ " \"code\": ";
+ string config_postfix = ","
+ " \"data\": \"192.0.2.1\","
+ " \"csv-format\": true"
+ " }],"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.50\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"interface\": \"\""
+ " } ]"
+ "}";
+
+ // There is docsis3 (vendor-id=4491) vendor option 2, which is a
+ // tftp-server. Its format is list of IPv4 addresses.
+ string config_valid = config_prefix + "2" + 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 = parseDHCP4(config_bogus));
+ ConstElementPtr json_valid;
+ ASSERT_NO_THROW(json_valid = parseDHCP4(config_valid));
+
+ NakedDhcpv4Srv srv(0);
+
+ // This should fail (missing option definition)
+ EXPECT_NO_THROW(x = configureDhcp4Server(srv, json_bogus));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(1, rcode_);
+
+ // This should work (option definition present)
+ EXPECT_NO_THROW(x = configureDhcp4Server(srv, json_valid));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+}
+
+/// Checks if DOCSIS client packets are classified properly
+///
+/// The test has been updated to work with the updated generic
+/// vendor options handling code.
+TEST_F(VendorOptsTest, docsisClientClassification) {
+
+ NakedDhcpv4Srv srv(0);
+
+ // Let's create a relayed DISCOVER. This particular relayed DISCOVER has
+ // vendor-class set to docsis3.0
+ Pkt4Ptr dis1;
+ ASSERT_NO_THROW(dis1 = PktCaptures::captureRelayedDiscover());
+ ASSERT_NO_THROW(dis1->unpack());
+
+ srv.classifyPacket(dis1);
+
+ EXPECT_TRUE(dis1->inClass(srv.VENDOR_CLASS_PREFIX + "docsis3.0:"));
+ EXPECT_FALSE(dis1->inClass(srv.VENDOR_CLASS_PREFIX + "eRouter1.0"));
+
+ // Let's create a relayed DISCOVER. This particular relayed DISCOVER has
+ // vendor-class set to eRouter1.0
+ Pkt4Ptr dis2;
+ ASSERT_NO_THROW(dis2 = PktCaptures::captureRelayedDiscover2());
+ ASSERT_NO_THROW(dis2->unpack());
+
+ srv.classifyPacket(dis2);
+
+ EXPECT_TRUE(dis2->inClass(srv.VENDOR_CLASS_PREFIX + "eRouter1.0"));
+ EXPECT_FALSE(dis2->inClass(srv.VENDOR_CLASS_PREFIX + "docsis3.0:"));
+}
+
+// Checks that it's possible to have a vivso (125) option in the response
+// only. Once specific client (Genexis) sends only vendor-class info and
+// expects the server to include vivso in the response.
+TEST_F(VendorOptsTest, vivsoInResponseOnly) {
+ Dhcp4Client client;
+
+ // The config defines custom vendor 125 suboption 2 that conveys a TFTP URL.
+ // The client doesn't send vendor 125 option, so normal vendor option
+ // processing is impossible. However, since there's a class defined that
+ // matches client's packets and that class inserts vivso in the response,
+ // Kea should be able to figure out the vendor-id and then also insert
+ // 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[60].hex,0,7) == 'HMC1000'\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"vivso-suboptions\","
+ " \"data\": \"25167\""
+ " },"
+ " {"
+ " \"name\": \"tftp\","
+ " \"space\": \"vendor-25167\","
+ " \"data\": \"tftp://192.0.2.1/genexis/HMC1000.v1.3.0-R.img\","
+ " \"always-send\": true"
+ " } ]"
+ " } ],"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.0/25\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"interface\": \"eth0\" "
+ " } ]"
+ "}";
+
+ EXPECT_NO_THROW(configure(config, *client.getServer()));
+
+ // Add a vendor-class identifier (this matches what Genexis hardware sends)
+ OptionPtr vopt(new OptionString(Option::V4, DHO_VENDOR_CLASS_IDENTIFIER,
+ "HMC1000.v1.3.0-R,Element-P1090,genexis.eu"));
+ client.addExtraOption(vopt);
+ client.requestOptions(DHO_VIVSO_SUBOPTIONS);
+
+ // Let's check whether the server is not able to process this packet
+ // and include vivso with appropriate sub-options
+ EXPECT_NO_THROW(client.doDiscover());
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check there's a response.
+ OptionPtr rsp = client.getContext().response_->getOption(DHO_VIVSO_SUBOPTIONS);
+ ASSERT_TRUE(rsp);
+
+ // Check that it includes vivso with vendor-id = 25167
+ OptionVendorPtr rsp_vivso = boost::dynamic_pointer_cast<OptionVendor>(rsp);
+ ASSERT_TRUE(rsp_vivso);
+ EXPECT_EQ(25167, rsp_vivso->getVendorId());
+
+ // Now check that it contains suboption 2 with appropriate content.
+ OptionPtr subopt2 = rsp_vivso->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);
+}
+
+// Verifies last resort option 43 is backward compatible
+TEST_F(VendorOptsTest, option43LastResort) {
+ NakedDhcpv4Srv srv(0);
+
+ // If there is no definition for option 43 a last resort
+ // one is applied. This definition was used by Kea <= 1.2
+ // so should be backward compatible.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"option-def\": [ "
+ "{ \"code\": 1, "
+ " \"name\": \"foo\", "
+ " \"space\": \"vendor-encapsulated-options-space\", "
+ " \"type\": \"uint32\" } ],"
+ "\"option-data\": [ "
+ "{ \"name\": \"foo\", "
+ " \"space\": \"vendor-encapsulated-options-space\", "
+ " \"data\": \"12345678\" }, "
+ "{ \"name\": \"vendor-class-identifier\", "
+ " \"data\": \"bar\" }, "
+ "{ \"name\": \"vendor-encapsulated-options\" } ] }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Create a packet with enough to select the subnet and go through
+ // the DISCOVER processing
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ query->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ query->addOption(clientid);
+ query->setIface("eth1");
+ query->setIndex(ETH1_INDEX);
+
+ // Create and add a PRL option to the query
+ OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+ DHO_DHCP_PARAMETER_REQUEST_LIST));
+ ASSERT_TRUE(prl);
+ prl->addValue(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ prl->addValue(DHO_VENDOR_CLASS_IDENTIFIER);
+ query->addOption(prl);
+
+ srv.classifyPacket(query);
+ ASSERT_NO_THROW(srv.deferredUnpack(query));
+
+ // Pass it to the server and get a DHCPOFFER.
+ Pkt4Ptr offer = srv.processDiscover(query);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Processing should add a vendor-class-identifier (code 60)
+ OptionPtr opt = offer->getOption(DHO_VENDOR_CLASS_IDENTIFIER);
+ EXPECT_TRUE(opt);
+
+ // And a vendor-encapsulated-options (code 43)
+ opt = offer->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ ASSERT_TRUE(opt);
+ const OptionCollection& opts = opt->getOptions();
+ ASSERT_EQ(1, opts.size());
+ OptionPtr sopt = opts.begin()->second;
+ ASSERT_TRUE(sopt);
+ EXPECT_EQ(1, sopt->getType());
+}
+
+// Checks effect of raw not compatible option 43 (no failure)
+TEST_F(VendorOptsTest, option43BadRaw) {
+ NakedDhcpv4Srv srv(0);
+
+ // The vendor-encapsulated-options has an incompatible data
+ // so won't have the expected content but processing of truncated
+ // (suboption length > available length) suboptions does not raise
+ // an exception.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"option-data\": [ "
+ "{ \"name\": \"vendor-class-identifier\", "
+ " \"data\": \"bar\" }, "
+ "{ \"name\": \"vendor-encapsulated-options\", "
+ " \"csv-format\": false, "
+ " \"data\": \"0102\" } ] }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Create a packet with enough to select the subnet and go through
+ // the DISCOVER processing
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ query->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ query->addOption(clientid);
+ query->setIface("eth1");
+ query->setIndex(ETH1_INDEX);
+
+ // Create and add a vendor-encapsulated-options (code 43)
+ // with not compatible (not parsable as suboptions) content
+ OptionBuffer buf;
+ buf.push_back(0x01);
+ buf.push_back(0x02);
+ OptionPtr vopt(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS, buf));
+ query->addOption(vopt);
+ query->getDeferredOptions().push_back(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+
+ // Create and add a PRL option to the query
+ OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+ DHO_DHCP_PARAMETER_REQUEST_LIST));
+ ASSERT_TRUE(prl);
+ prl->addValue(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ prl->addValue(DHO_VENDOR_CLASS_IDENTIFIER);
+ query->addOption(prl);
+
+ srv.classifyPacket(query);
+ srv.deferredUnpack(query);
+
+ // Check if the option was (uncorrectly) re-unpacked
+ vopt = query->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ OptionCustomPtr custom = boost::dynamic_pointer_cast<OptionCustom>(vopt);
+ EXPECT_TRUE(custom);
+
+ // Pass it to the server and get a DHCPOFFER.
+ Pkt4Ptr offer = srv.processDiscover(query);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Processing should add a vendor-class-identifier (code 60)
+ OptionPtr opt = offer->getOption(DHO_VENDOR_CLASS_IDENTIFIER);
+ EXPECT_TRUE(opt);
+
+ // And a vendor-encapsulated-options (code 43)
+ opt = offer->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ ASSERT_TRUE(opt);
+ // But truncated.
+ EXPECT_EQ(0, opt->len() - opt->getHeaderLen());
+}
+
+// Checks effect of raw not compatible option 43 (failure)
+TEST_F(VendorOptsTest, option43FailRaw) {
+ NakedDhcpv4Srv srv(0);
+
+ // The vendor-encapsulated-options has an incompatible data
+ // so won't have the expected content. Here the processing
+ // of suboptions tries to unpack the uitn32 foo suboption and
+ // raises an exception which is caught so the option stays unpacked.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"option-def\": [ "
+ "{ \"code\": 1, "
+ " \"name\": \"foo\", "
+ " \"space\": \"vendor-encapsulated-options-space\", "
+ " \"type\": \"uint32\" } ],"
+ "\"option-data\": [ "
+ "{ \"name\": \"vendor-class-identifier\", "
+ " \"data\": \"bar\" }, "
+ "{ \"name\": \"vendor-encapsulated-options\", "
+ " \"csv-format\": false, "
+ " \"data\": \"0102\" } ] }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Create a packet with enough to select the subnet and go through
+ // the DISCOVER processing
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ query->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ query->addOption(clientid);
+ query->setIface("eth1");
+ query->setIndex(ETH1_INDEX);
+
+ // Create and add a vendor-encapsulated-options (code 43)
+ // with not compatible (not parsable as suboptions) content
+ // which will raise an exception
+ OptionBuffer buf;
+ buf.push_back(0x01);
+ buf.push_back(0x01);
+ buf.push_back(0x01);
+ OptionPtr vopt(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS, buf));
+ query->addOption(vopt);
+ query->getDeferredOptions().push_back(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+
+ // Create and add a PRL option to the query
+ OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+ DHO_DHCP_PARAMETER_REQUEST_LIST));
+ ASSERT_TRUE(prl);
+ prl->addValue(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ prl->addValue(DHO_VENDOR_CLASS_IDENTIFIER);
+ query->addOption(prl);
+
+ srv.classifyPacket(query);
+ EXPECT_NO_THROW(srv.deferredUnpack(query));
+ ASSERT_TRUE(query->getOption(vopt->getType()));
+ EXPECT_EQ(vopt, query->getOption(vopt->getType()));
+}
+
+// Verifies raw option 43 can be handled (global)
+TEST_F(VendorOptsTest, option43RawGlobal) {
+ NakedDhcpv4Srv srv(0);
+
+ // The vendor-encapsulated-options is redefined as raw binary
+ // in a global definition.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"option-def\": [ "
+ "{ \"code\": 43, "
+ " \"name\": \"vendor-encapsulated-options\", "
+ " \"type\": \"binary\" } ],"
+ "\"option-data\": [ "
+ "{ \"name\": \"vendor-class-identifier\", "
+ " \"data\": \"bar\" }, "
+ "{ \"name\": \"vendor-encapsulated-options\", "
+ " \"csv-format\": false, "
+ " \"data\": \"0102\" } ] }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Create a packet with enough to select the subnet and go through
+ // the DISCOVER processing
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ query->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ query->addOption(clientid);
+ query->setIface("eth1");
+ query->setIndex(ETH1_INDEX);
+
+ // Create and add a vendor-encapsulated-options (code 43)
+ // with not compatible (not parsable as suboptions) content
+ OptionBuffer buf;
+ buf.push_back(0x02);
+ buf.push_back(0x03);
+ OptionPtr vopt(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS, buf));
+ query->addOption(vopt);
+ query->getDeferredOptions().push_back(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+
+ // Create and add a PRL option to the query
+ OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+ DHO_DHCP_PARAMETER_REQUEST_LIST));
+ ASSERT_TRUE(prl);
+ prl->addValue(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ prl->addValue(DHO_VENDOR_CLASS_IDENTIFIER);
+ query->addOption(prl);
+
+ srv.classifyPacket(query);
+ ASSERT_NO_THROW(srv.deferredUnpack(query));
+
+ // Check if the option was (correctly) re-unpacked
+ vopt = query->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ OptionCustomPtr custom = boost::dynamic_pointer_cast<OptionCustom>(vopt);
+ EXPECT_FALSE(custom);
+
+ // Pass it to the server and get a DHCPOFFER.
+ Pkt4Ptr offer = srv.processDiscover(query);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Processing should add a vendor-class-identifier (code 60)
+ OptionPtr opt = offer->getOption(DHO_VENDOR_CLASS_IDENTIFIER);
+ EXPECT_TRUE(opt);
+
+ // And a vendor-encapsulated-options (code 43)
+ opt = offer->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ ASSERT_TRUE(opt);
+ // Verifies the content
+ ASSERT_EQ(2, opt->len() - opt->getHeaderLen());
+ EXPECT_EQ(0x01, opt->getData()[0]);
+ EXPECT_EQ(0x02, opt->getData()[1]);
+}
+
+// Verifies raw option 43 can be handled (catch-all class)
+TEST_F(VendorOptsTest, option43RawClass) {
+ NakedDhcpv4Srv srv(0);
+
+ // The vendor-encapsulated-options is redefined as raw binary
+ // in a class definition.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"client-classes\": [ "
+ "{ \"name\": \"vendor\", "
+ " \"test\": \"option[vendor-encapsulated-options].exists\", "
+ " \"option-def\": [ "
+ " { \"code\": 43, "
+ " \"name\": \"vendor-encapsulated-options\", "
+ " \"type\": \"binary\" } ],"
+ " \"option-data\": [ "
+ " { \"name\": \"vendor-class-identifier\", "
+ " \"data\": \"bar\" }, "
+ " { \"name\": \"vendor-encapsulated-options\", "
+ " \"csv-format\": false, "
+ " \"data\": \"0102\" } ] } ] }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Create a packet with enough to select the subnet and go through
+ // the DISCOVER processing
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ query->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ query->addOption(clientid);
+ query->setIface("eth1");
+ query->setIndex(ETH1_INDEX);
+
+ // Create and add a vendor-encapsulated-options (code 43)
+ // with not compatible (not parsable as suboptions) content
+ OptionBuffer buf;
+ buf.push_back(0x02);
+ buf.push_back(0x03);
+ OptionPtr vopt(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS, buf));
+ query->addOption(vopt);
+ query->getDeferredOptions().push_back(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+
+ // Create and add a PRL option to the query
+ OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+ DHO_DHCP_PARAMETER_REQUEST_LIST));
+ ASSERT_TRUE(prl);
+ prl->addValue(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ prl->addValue(DHO_VENDOR_CLASS_IDENTIFIER);
+ query->addOption(prl);
+
+ srv.classifyPacket(query);
+ ASSERT_NO_THROW(srv.deferredUnpack(query));
+
+ // Check if the option was (correctly) re-unpacked
+ vopt = query->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ OptionCustomPtr custom = boost::dynamic_pointer_cast<OptionCustom>(vopt);
+ EXPECT_FALSE(custom);
+
+ // Pass it to the server and get a DHCPOFFER.
+ Pkt4Ptr offer = srv.processDiscover(query);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Processing should add a vendor-class-identifier (code 60)
+ OptionPtr opt = offer->getOption(DHO_VENDOR_CLASS_IDENTIFIER);
+ EXPECT_TRUE(opt);
+
+ // And a vendor-encapsulated-options (code 43)
+ opt = offer->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ ASSERT_TRUE(opt);
+ // Verifies the content
+ ASSERT_EQ(2, opt->len() - opt->getHeaderLen());
+ EXPECT_EQ(0x01, opt->getData()[0]);
+ EXPECT_EQ(0x02, opt->getData()[1]);
+}
+
+// Verifies option 43 deferred processing (one class)
+TEST_F(VendorOptsTest, option43Class) {
+ NakedDhcpv4Srv srv(0);
+
+ // A client class defines vendor-encapsulated-options (code 43)
+ // and data for it and its sub-option.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"option-def\": [ "
+ "{ \"code\": 1, "
+ " \"name\": \"foo\", "
+ " \"space\": \"alpha\", "
+ " \"type\": \"uint32\" } ],"
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"client-classes\": [ "
+ "{ \"name\": \"alpha\", "
+ " \"test\": \"option[vendor-class-identifier].text == 'alpha'\", "
+ " \"option-def\": [ "
+ " { \"code\": 43, "
+ " \"name\": \"vendor-encapsulated-options\", "
+ " \"type\": \"empty\", "
+ " \"encapsulate\": \"alpha\" } ],"
+ " \"option-data\": [ "
+ " { \"name\": \"vendor-class-identifier\", "
+ " \"data\": \"alpha\" }, "
+ " { \"name\": \"vendor-encapsulated-options\" }, "
+ " { \"name\": \"foo\", "
+ " \"space\": \"alpha\", "
+ " \"data\": \"12345678\" } ] } ] }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Create a packet with enough to select the subnet and go through
+ // the DISCOVER processing
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ query->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ query->addOption(clientid);
+ query->setIface("eth1");
+ query->setIndex(ETH1_INDEX);
+
+ // Create and add a vendor-encapsulated-options (code 43)
+ OptionBuffer buf;
+ buf.push_back(0x01);
+ buf.push_back(0x04);
+ buf.push_back(0x87);
+ buf.push_back(0x65);
+ buf.push_back(0x43);
+ buf.push_back(0x21);
+ OptionPtr vopt(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS, buf));
+ query->addOption(vopt);
+ query->getDeferredOptions().push_back(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+
+ // Create and add a vendor-class-identifier (code 60)
+ OptionStringPtr iopt(new OptionString(Option::V4,
+ DHO_VENDOR_CLASS_IDENTIFIER,
+ "alpha"));
+ query->addOption(iopt);
+
+ // Create and add a PRL option to the query
+ OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+ DHO_DHCP_PARAMETER_REQUEST_LIST));
+ ASSERT_TRUE(prl);
+ prl->addValue(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ prl->addValue(DHO_VENDOR_CLASS_IDENTIFIER);
+ query->addOption(prl);
+
+ srv.classifyPacket(query);
+ ASSERT_NO_THROW(srv.deferredUnpack(query));
+
+ // Check if the option was (correctly) re-unpacked
+ vopt = query->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ OptionCustomPtr custom = boost::dynamic_pointer_cast<OptionCustom>(vopt);
+ EXPECT_TRUE(custom);
+ EXPECT_EQ(1, vopt->getOptions().size());
+
+ // Pass it to the server and get a DHCPOFFER.
+ Pkt4Ptr offer = srv.processDiscover(query);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Processing should add a vendor-class-identifier (code 60)
+ OptionPtr opt = offer->getOption(DHO_VENDOR_CLASS_IDENTIFIER);
+ EXPECT_TRUE(opt);
+
+ // And a vendor-encapsulated-options (code 43)
+ opt = offer->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ ASSERT_TRUE(opt);
+ // Verifies the content
+ const OptionCollection& opts = opt->getOptions();
+ ASSERT_EQ(1, opts.size());
+ OptionPtr sopt = opts.begin()->second;
+ ASSERT_TRUE(sopt);
+ EXPECT_EQ(1, sopt->getType());
+ OptionUint32Ptr sopt32 = boost::dynamic_pointer_cast<OptionUint32>(sopt);
+ ASSERT_TRUE(sopt32);
+ EXPECT_EQ(12345678, sopt32->getValue());
+}
+
+// Verifies option 43 priority
+TEST_F(VendorOptsTest, option43ClassPriority) {
+ NakedDhcpv4Srv srv(0);
+
+ // Both global and client-class scopes get vendor-encapsulated-options
+ // (code 43) definition and data. The client-class has precedence.
+ // Note it does not work without the vendor-encapsulated-options
+ // option-data in the client-class.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"option-def\": [ "
+ "{ \"code\": 1, "
+ " \"name\": \"foo\", "
+ " \"space\": \"alpha\", "
+ " \"type\": \"uint32\" },"
+ "{ \"code\": 1, "
+ " \"name\": \"bar\", "
+ " \"space\": \"beta\", "
+ " \"type\": \"uint8\" }, "
+ "{ \"code\": 43, "
+ " \"name\": \"vendor-encapsulated-options\", "
+ " \"type\": \"empty\", "
+ " \"encapsulate\": \"beta\" } ],"
+ "\"option-data\": [ "
+ "{ \"name\": \"vendor-encapsulated-options\" }, "
+ "{ \"name\": \"vendor-class-identifier\", "
+ " \"data\": \"beta\" }, "
+ "{ \"name\": \"bar\", "
+ " \"space\": \"beta\", "
+ " \"data\": \"33\" } ],"
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"client-classes\": [ "
+ "{ \"name\": \"alpha\", "
+ " \"test\": \"option[vendor-class-identifier].text == 'alpha'\", "
+ " \"option-def\": [ "
+ " { \"code\": 43, "
+ " \"name\": \"vendor-encapsulated-options\", "
+ " \"type\": \"empty\", "
+ " \"encapsulate\": \"alpha\" } ],"
+ " \"option-data\": [ "
+ "{ \"name\": \"vendor-encapsulated-options\" }, "
+ " { \"name\": \"vendor-class-identifier\", "
+ " \"data\": \"alpha\" }, "
+ " { \"name\": \"foo\", "
+ " \"space\": \"alpha\", "
+ " \"data\": \"12345678\" } ] } ] }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Create a packet with enough to select the subnet and go through
+ // the DISCOVER processing
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ query->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ query->addOption(clientid);
+ query->setIface("eth1");
+ query->setIndex(ETH1_INDEX);
+
+ // Create and add a vendor-encapsulated-options (code 43)
+ OptionBuffer buf;
+ buf.push_back(0x01);
+ buf.push_back(0x04);
+ buf.push_back(0x87);
+ buf.push_back(0x65);
+ buf.push_back(0x43);
+ buf.push_back(0x21);
+ OptionPtr vopt(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS, buf));
+ query->addOption(vopt);
+ query->getDeferredOptions().push_back(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+
+ // Create and add a vendor-class-identifier (code 60)
+ OptionStringPtr iopt(new OptionString(Option::V4,
+ DHO_VENDOR_CLASS_IDENTIFIER,
+ "alpha"));
+ query->addOption(iopt);
+
+ // Create and add a PRL option to the query
+ OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+ DHO_DHCP_PARAMETER_REQUEST_LIST));
+ ASSERT_TRUE(prl);
+ prl->addValue(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ prl->addValue(DHO_VENDOR_CLASS_IDENTIFIER);
+ query->addOption(prl);
+
+ srv.classifyPacket(query);
+ ASSERT_NO_THROW(srv.deferredUnpack(query));
+
+ // Check if the option was (correctly) re-unpacked
+ vopt = query->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ OptionCustomPtr custom = boost::dynamic_pointer_cast<OptionCustom>(vopt);
+ EXPECT_TRUE(custom);
+ EXPECT_EQ(1, vopt->getOptions().size());
+
+ // Pass it to the server and get a DHCPOFFER.
+ Pkt4Ptr offer = srv.processDiscover(query);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Processing should add a vendor-class-identifier (code 60)
+ OptionPtr opt = offer->getOption(DHO_VENDOR_CLASS_IDENTIFIER);
+ EXPECT_TRUE(opt);
+ OptionStringPtr id = boost::dynamic_pointer_cast<OptionString>(opt);
+ ASSERT_TRUE(id);
+ EXPECT_EQ("alpha", id->getValue());
+
+ // And a vendor-encapsulated-options (code 43)
+ opt = offer->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ ASSERT_TRUE(opt);
+ // Verifies the content
+ const OptionCollection& opts = opt->getOptions();
+ ASSERT_EQ(1, opts.size());
+ OptionPtr sopt = opts.begin()->second;
+ ASSERT_TRUE(sopt);
+ EXPECT_EQ(1, sopt->getType());
+ EXPECT_EQ(2 + 4, sopt->len());
+ OptionUint32Ptr sopt32 = boost::dynamic_pointer_cast<OptionUint32>(sopt);
+ ASSERT_TRUE(sopt32);
+ EXPECT_EQ(12345678, sopt32->getValue());
+}
+
+// Verifies option 43 deferred processing (two classes)
+TEST_F(VendorOptsTest, option43Classes) {
+ NakedDhcpv4Srv srv(0);
+
+ // Two client-class scopes get vendor-encapsulated-options
+ // (code 43) definition and data. The first matching client-class
+ // (from a set?) applies.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"option-def\": [ "
+ "{ \"code\": 1, "
+ " \"name\": \"foo\", "
+ " \"space\": \"alpha\", "
+ " \"type\": \"uint32\" },"
+ "{ \"code\": 1, "
+ " \"name\": \"bar\", "
+ " \"space\": \"beta\", "
+ " \"type\": \"uint8\" } ],"
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"client-classes\": [ "
+ "{ \"name\": \"alpha\", "
+ " \"test\": \"option[vendor-class-identifier].text == 'alpha'\", "
+ " \"option-def\": [ "
+ " { \"code\": 43, "
+ " \"name\": \"vendor-encapsulated-options\", "
+ " \"type\": \"empty\", "
+ " \"encapsulate\": \"alpha\" } ],"
+ " \"option-data\": [ "
+ "{ \"name\": \"vendor-encapsulated-options\" }, "
+ " { \"name\": \"vendor-class-identifier\", "
+ " \"data\": \"alpha\" }, "
+ " { \"name\": \"foo\", "
+ " \"space\": \"alpha\", "
+ " \"data\": \"12345678\" } ] },"
+ "{ \"name\": \"beta\", "
+ " \"test\": \"option[vendor-class-identifier].text == 'beta'\", "
+ " \"option-def\": [ "
+ " { \"code\": 43, "
+ " \"name\": \"vendor-encapsulated-options\", "
+ " \"type\": \"empty\", "
+ " \"encapsulate\": \"beta\" } ],"
+ " \"option-data\": [ "
+ "{ \"name\": \"vendor-encapsulated-options\" }, "
+ " { \"name\": \"vendor-class-identifier\", "
+ " \"data\": \"beta\" }, "
+ " { \"name\": \"bar\", "
+ " \"space\": \"beta\", "
+ " \"data\": \"33\" } ] } ] }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Create a packet with enough to select the subnet and go through
+ // the DISCOVER processing
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ query->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ query->addOption(clientid);
+ query->setIface("eth1");
+ query->setIndex(ETH1_INDEX);
+
+ // Create and add a vendor-encapsulated-options (code 43)
+ OptionBuffer buf;
+ buf.push_back(0x01);
+ buf.push_back(0x04);
+ buf.push_back(0x87);
+ buf.push_back(0x65);
+ buf.push_back(0x43);
+ buf.push_back(0x21);
+ OptionPtr vopt(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS, buf));
+ query->addOption(vopt);
+ query->getDeferredOptions().push_back(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+
+ // Create and add a vendor-class-identifier (code 60)
+ OptionStringPtr iopt(new OptionString(Option::V4,
+ DHO_VENDOR_CLASS_IDENTIFIER,
+ "alpha"));
+ query->addOption(iopt);
+
+ // Create and add a PRL option to the query
+ OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+ DHO_DHCP_PARAMETER_REQUEST_LIST));
+ ASSERT_TRUE(prl);
+ prl->addValue(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ prl->addValue(DHO_VENDOR_CLASS_IDENTIFIER);
+ query->addOption(prl);
+
+ srv.classifyPacket(query);
+ ASSERT_NO_THROW(srv.deferredUnpack(query));
+
+ // Check if the option was (correctly) re-unpacked
+ vopt = query->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ OptionCustomPtr custom = boost::dynamic_pointer_cast<OptionCustom>(vopt);
+ EXPECT_TRUE(custom);
+ EXPECT_EQ(1, vopt->getOptions().size());
+
+ // Pass it to the server and get a DHCPOFFER.
+ Pkt4Ptr offer = srv.processDiscover(query);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Processing should add a vendor-class-identifier (code 60)
+ OptionPtr opt = offer->getOption(DHO_VENDOR_CLASS_IDENTIFIER);
+ EXPECT_TRUE(opt);
+ OptionStringPtr id = boost::dynamic_pointer_cast<OptionString>(opt);
+ ASSERT_TRUE(id);
+ EXPECT_EQ("alpha", id->getValue());
+
+ // And a vendor-encapsulated-options (code 43)
+ opt = offer->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ ASSERT_TRUE(opt);
+ // Verifies the content
+ const OptionCollection& opts = opt->getOptions();
+ ASSERT_EQ(1, opts.size());
+ OptionPtr sopt = opts.begin()->second;
+ ASSERT_TRUE(sopt);
+ EXPECT_EQ(1, sopt->getType());
+ EXPECT_EQ(2 + 4, sopt->len());
+ OptionUint32Ptr sopt32 = boost::dynamic_pointer_cast<OptionUint32>(sopt);
+ ASSERT_TRUE(sopt32);
+ EXPECT_EQ(12345678, sopt32->getValue());
+}
+
+// Checks effect of raw not compatible option 43 sent by a client (failure)
+TEST_F(VendorOptsTest, clientOption43FailRaw) {
+ Dhcp4Client client;
+
+ // The vendor-encapsulated-options has an incompatible data
+ // so won't have the expected content. Here the processing
+ // of suboptions tries to unpack the uint32 foo suboption and
+ // raises an exception which is caught.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"10.0.0.10 - 10.0.0.100\" } ], "
+ " \"subnet\": \"10.0.0.0/24\" } ],"
+ "\"option-def\": [ "
+ "{ \"code\": 1, "
+ " \"name\": \"foo\", "
+ " \"space\": \"vendor-encapsulated-options-space\", "
+ " \"type\": \"uint32\" } ] }";
+
+ EXPECT_NO_THROW(configure(config, *client.getServer()));
+
+ // Create and add a vendor-encapsulated-options (code 43)
+ // with not compatible (not parsable as suboptions) content
+ // which will raise an exception
+ OptionBuffer buf;
+ buf.push_back(0x01);
+ buf.push_back(0x01);
+ buf.push_back(0x01);
+ OptionPtr vopt(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS, buf));
+ client.addExtraOption(vopt);
+
+ // Let's check whether the server is not able to process this packet
+ // and raises an exception which is caught so the response is not empty.
+ EXPECT_NO_THROW(client.doDiscover());
+ EXPECT_TRUE(client.getContext().response_);
+}
+
+// Verifies raw option 43 sent by a client can be handled (global)
+TEST_F(VendorOptsTest, clientOption43RawGlobal) {
+ Dhcp4Client client;
+
+ // The vendor-encapsulated-options is redefined as raw binary
+ // in a global definition.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"10.0.0.10 - 10.0.0.100\" } ], "
+ " \"subnet\": \"10.0.0.0/24\" } ],"
+ "\"option-def\": [ "
+ "{ \"code\": 1, "
+ " \"name\": \"foo\", "
+ " \"space\": \"vendor-encapsulated-options-space\", "
+ " \"type\": \"uint32\" },"
+ "{ \"code\": 43, "
+ " \"name\": \"vendor-encapsulated-options\", "
+ " \"type\": \"binary\" } ],"
+ "\"option-data\": [ "
+ "{ \"name\": \"vendor-class-identifier\", "
+ " \"data\": \"bar\" }, "
+ "{ \"name\": \"vendor-encapsulated-options\", "
+ " \"csv-format\": false, "
+ " \"data\": \"0102\" } ] }";
+
+ EXPECT_NO_THROW(configure(config, *client.getServer()));
+
+ // Create and add a vendor-encapsulated-options (code 43)
+ // with not compatible (not parsable as suboptions) content
+ OptionBuffer buf;
+ buf.push_back(0x01);
+ buf.push_back(0x01);
+ buf.push_back(0x01);
+ OptionPtr vopt(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS, buf));
+ client.addExtraOption(vopt);
+
+ // Let's check whether the server is able to process this packet without
+ // throwing any exceptions so the response is not empty.
+ EXPECT_NO_THROW(client.doDiscover());
+ EXPECT_TRUE(client.getContext().response_);
+}
+
+// Verifies raw option 43 sent by a client can be handled (catch-all class)
+TEST_F(VendorOptsTest, clientOption43RawClass) {
+ Dhcp4Client client;
+
+ // The vendor-encapsulated-options is redefined as raw binary
+ // in a class definition.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { \"pool\": \"10.0.0.10 - 10.0.0.100\" } ], "
+ " \"subnet\": \"10.0.0.0/24\" } ],"
+ "\"option-def\": [ "
+ "{ \"code\": 1, "
+ " \"name\": \"foo\", "
+ " \"space\": \"vendor-encapsulated-options-space\", "
+ " \"type\": \"uint32\" } ],"
+ "\"client-classes\": [ "
+ "{ \"name\": \"vendor\", "
+ " \"test\": \"option[vendor-encapsulated-options].exists\", "
+ " \"option-def\": [ "
+ " { \"code\": 43, "
+ " \"name\": \"vendor-encapsulated-options\", "
+ " \"type\": \"binary\" } ],"
+ " \"option-data\": [ "
+ " { \"name\": \"vendor-class-identifier\", "
+ " \"data\": \"bar\" }, "
+ " { \"name\": \"vendor-encapsulated-options\", "
+ " \"csv-format\": false, "
+ " \"data\": \"0102\" } ] } ] }";
+
+ EXPECT_NO_THROW(configure(config, *client.getServer()));
+
+ // Create and add a vendor-encapsulated-options (code 43)
+ // with not compatible (not parsable as suboptions) content
+ OptionBuffer buf;
+ buf.push_back(0x01);
+ buf.push_back(0x01);
+ buf.push_back(0x01);
+ OptionPtr vopt(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS, buf));
+ client.addExtraOption(vopt);
+
+ // Let's check whether the server is able to process this packet without
+ // throwing any exceptions so the response is not empty.
+ EXPECT_NO_THROW(client.doDiscover());
+ EXPECT_TRUE(client.getContext().response_);
+}
+
+// Verifies that a client query with a truncated length in
+// vendor option (125) will still be processed by the server.
+TEST_F(Dhcpv4SrvTest, truncatedVIVSOOption) {
+ NakedDhcpv4Srv srv(0);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"10.206.80.0/25\" } ],"
+ " \"subnet\": \"10.206.80.0/24\", "
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"valid-lifetime\": 4000"
+ " } ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_) << isc::data::prettyPrint(status);
+
+ CfgMgr::instance().commit();
+
+ // Create a DISCOVER with a VIVSO option whose length is
+ // too short.
+ Pkt4Ptr dis;
+ ASSERT_NO_THROW(dis = PktCaptures::discoverWithTruncatedVIVSO());
+
+ // Simulate that we have received that traffic
+ srv.fakeReceive(dis);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered buffer4_receive callback.
+ srv.run();
+
+ // Check that the server did send a response
+ ASSERT_EQ(1, srv.fake_sent_.size());
+
+ // Make sure that we received an response and it was a DHCPOFFER.
+ Pkt4Ptr offer = srv.fake_sent_.front();
+ ASSERT_TRUE(offer);
+}
+
+/// Checks that it's possible to define and use a suboption 0.
+TEST_F(VendorOptsTest, vendorOpsSubOption0) {
+ NakedDhcpv4Srv srv(0);
+
+ // Zero Touch provisioning
+ string config =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"option-def\": ["
+ " {"
+ " \"name\": \"vendor-encapsulated-options\","
+ " \"code\": 43,"
+ " \"type\": \"empty\","
+ " \"encapsulate\": \"ZTP\""
+ " },"
+ " {"
+ " \"name\": \"config-file-name\","
+ " \"code\": 1,"
+ " \"space\": \"ZTP\","
+ " \"type\": \"string\""
+ " },"
+ " {"
+ " \"name\": \"image-file-name\","
+ " \"code\": 0,"
+ " \"space\": \"ZTP\","
+ " \"type\": \"string\""
+ " },"
+ " {"
+ " \"name\": \"image-file-type\","
+ " \"code\": 2,"
+ " \"space\": \"ZTP\","
+ " \"type\": \"string\""
+ " },"
+ " {"
+ " \"name\": \"transfer-mode\","
+ " \"code\": 3,"
+ " \"space\": \"ZTP\","
+ " \"type\": \"string\""
+ " },"
+ " {"
+ " \"name\": \"all-image-file-name\","
+ " \"code\": 4,"
+ " \"space\": \"ZTP\","
+ " \"type\": \"string\""
+ " },"
+ " {"
+ " \"name\": \"http-port\","
+ " \"code\": 5,"
+ " \"space\": \"ZTP\","
+ " \"type\": \"string\""
+ " }"
+ " ],"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"vendor-encapsulated-options\""
+ " },"
+ " {"
+ " \"name\": \"image-file-name\","
+ " \"data\": \"/dist/images/jinstall-ex.tgz\","
+ " \"space\": \"ZTP\""
+ " }"
+ " ],"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config, true));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Create a packet with enough to select the subnet and go through
+ // the DISCOVER processing
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ query->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ query->addOption(clientid);
+ query->setIface("eth1");
+ query->setIndex(ETH1_INDEX);
+
+ // Create and add a PRL option to the query
+ OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+ DHO_DHCP_PARAMETER_REQUEST_LIST));
+ ASSERT_TRUE(prl);
+ prl->addValue(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ prl->addValue(DHO_VENDOR_CLASS_IDENTIFIER);
+ query->addOption(prl);
+
+ srv.classifyPacket(query);
+ ASSERT_NO_THROW(srv.deferredUnpack(query));
+
+ // Pass it to the server and get a DHCPOFFER.
+ Pkt4Ptr offer = srv.processDiscover(query);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Processing should add a vendor-encapsulated-options (code 43)
+ OptionPtr opt = offer->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ ASSERT_TRUE(opt);
+ const OptionCollection& opts = opt->getOptions();
+ ASSERT_EQ(1, opts.size());
+ OptionPtr sopt = opts.begin()->second;
+ ASSERT_TRUE(sopt);
+ EXPECT_EQ(0, sopt->getType());
+
+ // Check suboption 0 content.
+ OptionStringPtr sopt0 =
+ boost::dynamic_pointer_cast<OptionString>(sopt);
+ ASSERT_TRUE(sopt0);
+ EXPECT_EQ("/dist/images/jinstall-ex.tgz", sopt0->getValue());
+}