diff options
Diffstat (limited to 'src/bin/dhcp4')
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='[0;31m'; \ + grn='[0;32m'; \ + lgn='[1;32m'; \ + blu='[1;34m'; \ + mgn='[0;35m'; \ + brg='[1m'; \ + std='[m'; \ + 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()); +} |